Message Scroller
Chat transcript scroller for turn anchoring, streaming follow, history restore, and jump controls.
MessageScroller owns the transcript viewport: turn anchoring, live-edge follow, opening position, prepend restore, jump commands, and visibility. It does not own messages, transport, or the prompt composer - pair it with Message and Bubble.
Installation
pnpm dlx cubix@latest add message-scroller --base radixUsage
import {
MessageScroller,
MessageScrollerButton,
MessageScrollerContent,
MessageScrollerItem,
MessageScrollerProvider,
MessageScrollerViewport,
} from "@/components/cubix/message-scroller"<MessageScrollerProvider autoScroll>
<MessageScroller>
<MessageScrollerViewport>
<MessageScrollerContent>
{messages.map((message) => (
<MessageScrollerItem
key={message.id}
messageId={message.id}
scrollAnchor={message.role === "user"}
>
<Message>...</Message>
</MessageScrollerItem>
))}
</MessageScrollerContent>
</MessageScrollerViewport>
<MessageScrollerButton />
</MessageScroller>
</MessageScrollerProvider>Note: MessageScroller fills its parent. Place it in a height-constrained container. Wrap every direct content child in MessageScrollerItem.
Composition
MessageScrollerProvider
└── MessageScroller
├── MessageScrollerViewport
│ └── MessageScrollerContent
│ └── MessageScrollerItem…
├── MessageScrollerButton (start)
└── MessageScrollerButton (end)Features
- Anchors new turns near the top with previous-item peek
- Follows streamed output only while the reader is at the live edge
- Opens saved threads on start, end, or last-anchor
- Preserves place when earlier history is prepended
- Jumps to any messageId from outside the list
- Tracks current anchor and visible rows on demand
- Start and end scroll buttons that stay inert until useful
Anchoring turns
Mark the row that starts a turn with scrollAnchor. Send a turn and toggle which role settles near the top.
Following the live edge
With autoScroll, tokens stay in view while the reader remains at the bottom. Scroll away and the position is preserved.
Opening saved threads
Use defaultScrollPosition="last-anchor" so a saved transcript opens on the last meaningful turn.
Loading earlier messages
Prepend older rows without jumping the reader. Stable messageId values keep the visible row locked.
Jumping to messages
Drive the transcript from outside with useMessageScroller.
Tracking position
useMessageScrollerVisibility reports the current anchor and visible ids. Tracking only runs while something subscribes.
Current anchor: none · Visible: 0
Scrollable edges
useMessageScrollerScrollable reports whether the viewport can still move toward start or end.
The transcript fits in the viewport.
Group chat anchors
Anchors are role-independent. Mark a join marker as the turn boundary when a participant enters.
API Reference
MessageScrollerProvider
| Prop | Type | Default | Description |
|---|---|---|---|
| autoScroll | boolean | false | Follow streamed output while the reader stays at the live edge. |
| defaultScrollPosition | "start" | "end" | "last-anchor" | "start" | Where a mounted transcript opens. |
| scrollPreviousItemPeek | number | - | Pixels of the previous item kept visible above a newly anchored turn. |
| scrollEdgeThreshold | number | - | Distance from an edge that still counts as being at that edge. |
| scrollMargin | number | - | Default scroll margin used by programmatic scroll commands. |
MessageScroller
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | Additional classes for the scroller frame. |
MessageScrollerViewport
| Prop | Type | Default | Description |
|---|---|---|---|
| preserveScrollOnPrepend | boolean | true | Keep the visible row stable when earlier messages are prepended. |
| className | string | - | Additional classes for the scrollable viewport. |
MessageScrollerContent
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | Additional classes for the transcript container. |
| spacerClassName | string | - | Optional classes for the content spacer used by anchoring. |
MessageScrollerItem
| Prop | Type | Default | Description |
|---|---|---|---|
| messageId | string | - | Stable id for jump targets, visibility tracking, and prepend restore. |
| scrollAnchor | boolean | false | Mark this row as the start of a turn for anchoring. |
| className | string | - | Additional classes for the transcript row. |
MessageScrollerButton
| Prop | Type | Default | Description |
|---|---|---|---|
| direction | "start" | "end" | "end" | Which edge the button scrolls toward. |
| behavior | ScrollBehavior | - | Native scroll behavior for the button action. |
| variant | Button variant | "secondary" | Passed through to the Cubix Button render target. |
| size | Button size | "icon-sm" | Passed through to the Cubix Button render target. |