Chat Conversation
Scrolling log for AI chat that follows streamed replies and lets go when the reader scrolls up.
Description
ChatConversation is the scroll container around a chat thread. The root renders a <div role="log"> that owns the vertical scroll, ChatConversation.Content is the message column inside it, and ChatConversation.ScrollButton is the optional button that brings the reader back to the newest message. It mounts parked at the bottom and follows new content while the reader stays there.
The moment the reader scrolls up (wheel, touch, keyboard, scrollbar), it stops following, so a long streamed answer never drags them away from the paragraph they are reading. Scrolling back to the bottom, or pressing the scroll button, re-attaches it. Use it for assistant panels, support chats and full-page threads, with ChatMessage turns inside.
Don't use it for content that doesn't grow at the end. A settings page, a document or a sidebar list should use Scroll Area or plain overflow. For a list of past threads use Chat List View, and for the messages themselves use Chat Message.
ChatConversation is a Create UI Pro component. Install it with a Pro seat.
Installation
Anatomy
Usage
Examples
Every example is a Pro preview (marked with a Pro badge).
Without fade
By default the top and bottom edges fade while there is content past them, and each edge stops fading once the viewport reaches it. fade={false} turns the mask off, which suits a conversation sitting on a busy or bordered surface.
Scroll button
Scroll up and ChatConversation.ScrollButton appears above the bottom edge. It stays mounted while hidden, so it never shifts the layout. Pass tooltip for a hover label and aria-label to describe where it goes.
Empty state
ChatConversation.Content is a flex column, so flex-1 items-center justify-center centers a first-run message in the viewport. Swap it for the thread once the first message is sent.
Streaming
Pass isStreaming while a reply is arriving. The log gets aria-busy, so screen readers announce the finished answer once instead of every token. Scroll up mid-stream to see the viewport let go.
Scroll on send
useChatConversationContext() exposes scrollToBottom and stopScroll to any component inside the root. Call scrollToBottom() after the user sends a message, so their own message is in view even if they had scrolled up to reread something.
Accessibility
The log is a tab stop, so keyboard users can scroll it in every browser, including Safari, which doesn't make scroll containers focusable on its own.
ARIA notes:
- The root renders
role="log", which is a polite live region: messages added at the end are announced. - The root has
aria-label="Conversation"by default. Pass your ownaria-labeloraria-labelledbyto name the thread or translate it. isStreamingsetsaria-busyon the log, which holds announcements until the reply is complete. Setaria-busyon the streamingChatMessageas well.- The scroll button is labelled "Scroll to latest message". While hidden it is
disabledand its wrapper isaria-hidden, so it leaves the tab order. - Code blocks and tables inside messages scroll on their own. Scrolling one of them does not release the conversation until it reaches its own edge.
prefers-reduced-motionturns smooth scrolling into an instant jump and removes the fade transition.
Styling
Tailwind override: pass className to the root to size it, and to Content to set the column width and padding. Content has a 32px gap between messages by default.
Data slots and attributes: the component sets these for CSS targeting:
data-slot="chat-conversation"on the scrolling root.data-slot="chat-conversation-content"on the message column.data-slot="chat-conversation-scroll-button-container"on the sticky wrapper around the button.data-slot="chat-conversation-scroll-button"on the button, plus theButtonattributes.data-stateon the root:"at-bottom"while parked at the bottom,"scrolled"otherwise.data-at-topon the root while it is parked at the top.data-fadeon the root whilefadeis on.data-streamingon the root whileisStreamingis on.data-stateon the button container:"visible"or"hidden".
The fade reads --chat-conversation-fade-size (default 32px). The component writes --chat-conversation-scrollbar-left and --chat-conversation-scrollbar-right on the root with the measured scrollbar width, so the mask never covers a classic scrollbar.
Related Components
- Chat Message: the user and assistant turns that go inside
ChatConversation.Content. - Markdown: renders assistant replies, with
isStreamingfor the reply that is still arriving. - Chat Loader: the placeholder at the end of the log before the first token arrives.
- Scroll Area: use it for scroll regions that don't need to follow new content.
API Reference
ChatConversation
The scrolling log. Tracks the scroll position, follows new content while parked at the bottom and provides the context the other parts read. Extends React.ComponentProps<"div">.
Props
ChatConversation.Content
The message column: a centered flex column with a 32px gap. The root watches its size, which is how a streamed token or an expanded block keeps the view pinned. Extends React.ComponentProps<"div">. Also exported as ChatConversationContent.
Props
ChatConversation.ScrollButton
An icon-only, outline pill Button that scrolls to the newest message and follows again. Renders nothing outside a ChatConversation. Extends the Button props. Also exported as ChatConversationScrollButton.
Props
useChatConversationContext
Returns the conversation state and controls, or null outside a ChatConversation. Use it in components rendered inside the root.
Types
chatConversationVariants, chatConversationContentVariants, chatConversationScrollButtonVariants and chatConversationScrollButtonContainerVariants are exported for composing your own parts.