Message Scroller

A chat scroll container for message transcripts.

@react.component
let make = () =>
  <Card className="h-[480px] w-full max-w-xl">

MessageScroller is a chat transcript scroller built on the @shadcn/react primitives. It provides the styled ReScript surface for opening position, anchored turns, scroll preservation, and jump controls while leaving message rendering to Message, Bubble, Marker, and Attachment.

Installation

npx shadcn@latest add @rescript-shadcn/MessageScroller

Usage

<MessageScroller.Provider defaultScrollPosition=LastAnchor>
  <MessageScroller>
    <MessageScroller.Viewport>
      <MessageScroller.Content>
        <MessageScroller.Item messageId="question" scrollAnchor=true>
          <Message align=End>
            <Message.Content>
              <Bubble>
                <Bubble.Content> {"Can you check the docs?"->React.string} </Bubble.Content>
              </Bubble>
            </Message.Content>
          </Message>
        </MessageScroller.Item>
      </MessageScroller.Content>
    </MessageScroller.Viewport>
    <MessageScroller.Button ariaLabel="Scroll to latest" />
  </MessageScroller>
</MessageScroller.Provider>

MessageScroller fills its parent, so place it inside a height-constrained container.

Composition

MessageScroller.Provider
└── MessageScroller
    ├── MessageScroller.Viewport
    │   └── MessageScroller.Content
    │       ├── MessageScroller.Item
    │       └── MessageScroller.Item
    └── MessageScroller.Button
  • MessageScroller.Provider owns scroll state and behavior props.
  • MessageScroller is the styled frame.
  • MessageScroller.Viewport is the scrollable element.
  • MessageScroller.Content holds transcript rows.
  • MessageScroller.Item wraps every direct row so the primitive can measure, anchor, preserve position, and jump to it.
  • MessageScroller.Button scrolls to the start or end and is inert when there is no content in that direction.

Core Concepts

Anchoring Turns

Mark the row that starts a meaningful turn with scrollAnchor=true. Use stable messageId values so the scroller can preserve and target rows.

<MessageScroller.Item messageId=message.id scrollAnchor={message.role == "user"}>
  <Message />
</MessageScroller.Item>

Opening Position

Set defaultScrollPosition=LastAnchor to open a saved transcript at the latest anchored turn instead of the absolute bottom.

Loading Earlier Messages

MessageScroller.Viewport preserves the visible row when older messages are prepended. This is enabled by default with preserveScrollOnPrepend=true.

Accessibility

MessageScroller.Viewport is a keyboard-focusable scroll region by default in the upstream primitive. MessageScroller.Content uses log-region defaults for additions. Pass ariaBusy=true while a turn streams if announcements should wait for the completed row.

API Reference

MessageScroller.Provider

PropTypeDefault
autoScrollboolfalse
defaultScrollPositionStart | End | LastAnchorEnd
scrollEdgeThresholdfloat-
scrollPreviousItemPeekfloat-
scrollMarginfloat-

MessageScroller.Item

PropTypeDefault
messageIdstring-
scrollAnchorboolfalse

MessageScroller.Button

PropTypeDefault
directionStart | EndEnd
behaviorAuto | Instant | Smooth-