Message Scroller
A chat transcript that follows new messages, anchors each turn and keeps the reader's place.
Message Scroller ports @shadcn/react’s message scroller. It follows that package’s behavior, not Base UI’s.
Anatomy
Import the component and assemble its parts:
Examples
Anchoring turns
Set scrollAnchor on the row that starts a turn, such as the user’s message. When a new anchored row arrives, the viewport places it near the top, with scrollPreviousItemPeek pixels of the previous row still visible, and holds it there while the reply streams in below. With autoScroll, the viewport follows the end again once the reply fills the space below the anchor.
Avoiding a flash on reload
For the 'end' and 'last-anchor' opening positions, the root and the viewport have data-pending-scroll until the position is applied. Hide the viewport with visibility: hidden while it is set, so the layout still exists for the opening scroll:
Reading scroll state
getMessageScroller() returns the scroller of the closest provider. Call it while a component initializes. Its scrollable and visibility getters are reactive. Visibility is tracked only while something reads it.
API reference
Provider
Owns the scroll state and the behavior props, and provides them to the parts and getMessageScroller(). Doesn't render its own HTML element.
autoScroll boolean false
- Name
- Description
Follow new content at the bottom while the viewport is already at the end.
- Type
boolean- Default
false
defaultScrollPosition MessageScrollerDefaultScrollPosition 'end'
- Description
Opening position on the first non-empty render, applied once.
- Type
MessageScrollerDefaultScrollPosition- Default
'end'
scrollEdgeThreshold number 8
- Name
- Description
Distance from an edge that still counts as at the start or end.
- Type
number- Default
8
scrollMargin number 0
- Name
- Description
Default margin on the aligned edge for commands and visibility.
- Type
number- Default
0
scrollPreviousItemPeek number 64
- Description
Extra top margin for a newly anchored row, added to
scrollMargin.- Type
number- Default
64
Root
The frame around the viewport. It mirrors the viewport's scroll-state attributes. Renders a <div> element.
This part takes no props beyond its element's HTML attributes.
data-autoscrolling
data-pending-scroll
'end' or 'last-anchor' opening position is applied. Hide the viewport with visibility: hidden while it is set.data-scrollable
start, end or start end. Absent when the content fits. Match one edge with [data-scrollable~="end"].Viewport
The scrollable, focusable transcript region. Renders a <div> element.
preserveScrollOnPrepend boolean true
- Description
Keep the first visible
messageIdrow stable when rows are prepended.- Type
boolean- Default
true
data-autoscrolling
data-pending-scroll
'end' or 'last-anchor' opening position is applied. Hide the viewport with visibility: hidden while it is set.data-scrollable
start, end or start end. Absent when the content fits. Match one edge with [data-scrollable~="end"].Content
The transcript log. Every direct child should be a MessageScroller.Item. Renders a <div> element.
spacerClass string —
- Name
- Description
Class for the internal tail spacer used when anchoring turns near the top.
- Type
string
Item
One transcript row: a message, marker, typing row, separator or load-more row. Renders a <div> element.
messageId string —
- Name
- Description
Stable row id for
scrollToMessage, visibility and prepend preservation.- Type
string
scrollAnchor boolean false
- Name
- Description
Marks a turn boundary that newly appended anchors and
last-anchorrestore use.- Type
boolean- Default
false
data-message-id
messageId.data-scroll-anchor
Button
Scrolls to the start or end of the transcript. It is inert and out of the tab order while there is nothing to scroll toward. Renders a <button> element.
behavior ScrollBehavior 'smooth'
- Name
- Description
Native scroll behavior when clicked.
- Type
ScrollBehavior- Default
'smooth'
direction MessageScrollerButtonDirection 'end'
- Name
- Description
Transcript edge to scroll toward.
- Type
MessageScrollerButtonDirection- Default
'end'
render Snippet<[props: HTMLButtonAttributes, state: MessageScrollerButtonState]> —
- Name
- Description
Replaces the default
<button>. Spreadpropsonto the host element.- Type
Snippet<[props: HTMLButtonAttributes, state: MessageScrollerButtonState]>
active boolean
- Name
active- Description
Whether content is hidden toward this button's direction.
- Type
boolean
direction MessageScrollerButtonDirection
- Name
direction- Type
MessageScrollerButtonDirection