Make your AI a shadcn expert

Shadcn Message Scroller

React chat scroller for AI threads and support transcripts. Built with TypeScript and Tailwind CSS for Next.js using Base UI — anchors turns, follows streams.

A message scroller is a height-constrained transcript. New turns pin near the top, streamed replies follow only while you sit at the live edge, and older history can load above without moving the row you are reading. Use it for AI chats, support inboxes, and any thread that should not yank the reader around.

Scroll to load preview...

Usage

import { Message } from "@/components/ui/message"
import {
  MessageScroller,
  MessageScrollerButton,
  MessageScrollerContent,
  MessageScrollerItem,
  MessageScrollerProvider,
  MessageScrollerViewport,
} from "@/components/ui/message-scroller"
<MessageScrollerProvider>
  <MessageScroller>
    <MessageScrollerViewport>
      <MessageScrollerContent>
        {messages.map((message) => (
          <MessageScrollerItem
            key={message.id}
            messageId={message.id}
            scrollAnchor={message.role === "user"}
          >
            <Message />
          </MessageScrollerItem>
        ))}
      </MessageScrollerContent>
    </MessageScrollerViewport>
    <MessageScrollerButton />
  </MessageScroller>
</MessageScrollerProvider>

MessageScroller fills its parent — give that parent a height. scrollAnchor marks the start of a turn.

Composition

MessageScrollerItem
MessageScrollerItem
MessageScrollerButton

Anchoring

scrollAnchor pins that row near the top and leaves a peek of the previous turn.

Scroll to load preview...

Group Chat

Anchor a marker the same way — a join event can start the turn, not just a user message.

Scroll to load preview...

Previous Context

scrollPreviousItemPeek keeps pixels of the last row visible above the new anchor.

Scroll to load preview...

AI SDK

A scripted conversation through @shadcn/helpers/ai-sdk and useChat. No model, no API key — press send to stream the next turn.

Scroll to load preview...

TanStack AI

Same transcript through @shadcn/helpers/tanstack-ai. Same scroller, different chat hook.

Scroll to load preview...

Streaming

autoScroll follows tokens only while the reader sits at the live edge.

Scroll to load preview...

Opening Position

defaultScrollPosition="last-anchor" reopens a saved thread on the last meaningful turn.

Scroll to load preview...

Load History

Prepend older rows; preserveScrollOnPrepend keeps the visible message still.

Scroll to load preview...

Animation

Animate transform and opacity on MessageScrollerItem — not height, margin, or padding.

Scroll to load preview...

Commands

useMessageScroller jumps from a menu or permalink, even outside the frame.

Scroll to load preview...

Visibility

useMessageScrollerVisibility reports the current anchor and what is on screen.

Scroll to load preview...

Scrollable

useMessageScrollerScrollable tells you which edges still have overflow.

Scroll to load preview...

API

Prop

Type

These props live on MessageScrollerProvider. MessageScroller is the styled frame and takes className.

MessageScrollerItem

Prop

Type

MessageScrollerViewport

Prop

Type

MessageScrollerButton

Prop

Type

MessageScrollerContent is a role="log" live region. Pass aria-busy while a turn streams. Hooks — useMessageScroller, useMessageScrollerVisibility, useMessageScrollerScrollable — read from the provider, so they work outside the frame.

Keyboard

KeyAction
TabFocus the viewport
Arrow / Page / Home / End / SpaceScroll; also releases auto-follow
Enter / Space on the buttonJump to the latest message

Notes

The frame fills its parent

Give the parent a height — h-[350px], flex-1 in a column, or h-dvh. An unconstrained parent grows the page.

Wrap every direct child of MessageScrollerContent in MessageScrollerItem. Parts throw without the provider.

With other components

Questions

Was this page helpful?

Sign in to leave feedback.