Component

Message scroller

A chat-style scroll container that sticks to the latest message. New content auto-scrolls while pinned to the bottom, a jump button appears when you scroll up, and the instance exposes methods to scroll to the start, end, or any message.

Auto-scroll & jump button

Autumn note #1 β€” leaves, weather, and city recorded.
Autumn note #2 β€” leaves, weather, and city recorded.
Autumn note #3 β€” leaves, weather, and city recorded.
Autumn note #4 β€” leaves, weather, and city recorded.
Autumn note #5 β€” leaves, weather, and city recorded.
Autumn note #6 β€” leaves, weather, and city recorded.
Autumn note #7 β€” leaves, weather, and city recorded.
Autumn note #8 β€” leaves, weather, and city recorded.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

import { HbButtonComponent, HbMessageScrollerImports } from '@herbst/ui';

@Component({
  selector: 'hb-demo-message-scroller-basic',
  imports: [HbMessageScrollerImports, HbButtonComponent],
  template: `
    <div class="flex w-full max-w-md flex-col gap-2">
      <hb-message-scroller hbAutoScroll class="h-64 rounded-md border border-border">
        <hb-message-scroller-viewport>
          <hb-message-scroller-content class="flex flex-col gap-2 p-3">
            @for (line of lines(); track $index) {
              <hb-message-scroller-item [hbMessageId]="'m-' + $index">
                <div class="w-fit rounded-lg bg-muted px-3 py-2 text-sm">{{ line }}</div>
              </hb-message-scroller-item>
            }
          </hb-message-scroller-content>
        </hb-message-scroller-viewport>

        <hb-message-scroller-button />
      </hb-message-scroller>

      <button hb-button hbSize="sm" class="self-start" (click)="send()">Send message</button>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HbDemoMessageScrollerBasicComponent {
  protected readonly lines = signal(
    Array.from(
      { length: 8 },
      (_, i) => `Autumn note #${i + 1} β€” leaves, weather, and city recorded.`,
    ),
  );

  protected send(): void {
    this.lines.update((list) => [...list, `New note #${list.length + 1} just added.`]);
  }
}

Programmatic controls & anchors

Message #1
Message #2
Message #3
Message #4
Message #5
Message #6
Message #7
Message #8
Message #9
Message #10 Β· anchor
Message #11
Message #12
Message #13
Message #14
Message #15
Message #16

atBottom: true

API reference

hb-message-scroller

PropertyDescriptionTypeDefault
[hbAutoScroll] Keep the view pinned to the bottom as new content arrives β€” until the user scrolls up. boolean true
[hbDefaultScrollPosition] Where to land on first render: the end, the start, or the last anchored item. 'end' | 'start' | 'last-anchor' 'end'
[hbScrollPreviousItemPeek] Pixels of the previous item to leave visible when scrolling to a message or anchor. number 0
exportAs "hbMessageScroller" Reference the instance to call scrollToEnd(), scrollToStart(), scrollToMessage(id), and read the atBottom(), scrollableStart(), scrollableEnd() signals. methods / signals β€”

Parts

PropertyDescriptionTypeDefault
hb-message-scroller-viewport The scrollable region. Must wrap the content and drives the scroll tracking. component β€”
hb-message-scroller-content The inner wrapper whose size is observed to trigger auto-scroll. component β€”
hb-message-scroller-item [hbMessageId] [hbScrollAnchor] Wraps each message; the id is the scrollToMessage target, and hbScrollAnchor marks it for last-anchor positioning. string / boolean '' / false
hb-message-scroller-button [hbIcon] A floating jump-to-latest button, shown only when there is content below. hbIcon sets the icon name. string 'phosphorCaretDown'