Message Scroller

A chat transcript scroller for turn anchoring, follow-output while streaming, history prepend without jumps, and jump-to-message commands. Put it in a height-constrained parent — it fills available space.

Basic

Wrap each row in MessageScrollerItem. Mark turn starts with scrollAnchor.

Installation

CLI
npx @sonojs/ui@latest add message-scroller
Also needsnpx @sonojs/ui@latest add button && npm i lucide-react

Composition

structure
MessageScrollerProvider
└── MessageScroller
    ├── MessageScrollerViewport
    │   └── MessageScrollerContent
    │       └── MessageScrollerItem
    └── MessageScrollerButton

Usage

message-scroller.tsx
import {
  Message,
  MessageContent,
} from "@/components/ui/message"
import {
  MessageScroller,
  MessageScrollerButton,
  MessageScrollerContent,
  MessageScrollerItem,
  MessageScrollerProvider,
  MessageScrollerViewport,
} from "@/components/ui/message-scroller"

export function Example({ messages }) {
  return (
    <div className="flex h-screen flex-col">
      <MessageScrollerProvider autoScroll>
        <MessageScroller className="flex-1">
          <MessageScrollerViewport>
            <MessageScrollerContent>
              {messages.map((message) => (
                <MessageScrollerItem
                  key={message.id}
                  messageId={message.id}
                  scrollAnchor={message.role === "user"}
                >
                  <Message align={message.role === "user" ? "end" : "start"}>
                    <MessageContent>{/* Bubble */}</MessageContent>
                  </Message>
                </MessageScrollerItem>
              ))}
            </MessageScrollerContent>
          </MessageScrollerViewport>
          <MessageScrollerButton />
        </MessageScroller>
      </MessageScrollerProvider>
    </div>
  )
}

Props

PropTypeDefaultDescription
autoScrollbooleanfalseOn `MessageScrollerProvider` — follow the live edge until the reader scrolls away.
defaultScrollPosition"start" · "end" · "last-anchor""end"Where a transcript opens on first paint.
scrollPreviousItemPeeknumber48Pixels of the previous row kept visible above an anchor.
scrollAnchorbooleanfalseOn `MessageScrollerItem` — mark the start of a turn.
messageIdstring—Stable id for jump / visibility tracking.
direction"start" · "end""end"On `MessageScrollerButton` — vertical transcript edge to jump to (not writing direction). Row LTR/RTL comes from `Message` `align` + inherited `dir`.