Conversation
A transcript that follows streaming text, stops when you scroll up and offers Jump to latest with an unread count.
Built on Icons.
Installation
npx shadcn@latest add https://neonkit.heyaadi.com/r/conversation.jsonUsage
import { Conversation, ConversationDivider } from "@/components/ui/conversation"
<Conversation className="h-[600px]" streaming={streaming}>
<ConversationDivider>Today</ConversationDivider>
{messages.map((m) => <ChatMessage key={m.id} {...m} />)}
</Conversation>Examples
A debugging chat
Conversation, ChatMessage, PromptInput and CodeBlock together. The reply thinks, searches, reads a file and streams a fix as a diff; send a follow-up to get a test.
300 messages
Scroll up and new messages stop pulling you down. Jump to latest appears with a count of what arrived since.
Empty state
The empty slot fills the space until the first message. Pick a suggestion or type one.
Dates and system notes
ConversationDivider separates days. initialScroll="top" opens a thread at its start.
API reference
Conversation
Renders a div around a scrolling log. Give it a height, or put it in a flex column.
| Prop | Type | Default |
|---|---|---|
empty | ReactNodeShown when there are no children. | None |
streaming | booleanSets aria-busy so screen readers wait for the finished reply. | false |
autoScroll | booleanFollow new content while the reader is at the bottom. | true |
threshold | numberPixels from the bottom that still count as at the bottom. | 56 |
initialScroll | "bottom"Where it opens. | "bottom" |
jumpLabel | stringText on the pill. | "Jump to latest" |
contentClassName | stringClasses for the column holding the messages. | None |
actionsRef | Ref<{ scrollToBottom, isAtBottom }>Imperative controls, e.g. to jump down after sending from elsewhere. | None |
onStickChange | (stuck: boolean) => voidCalled when following starts or stops. | None |
color | NeonColorPalette name or hex. Lights the unread count. | inherited |
ConversationDivider
A labelled separator. Not counted as an unread message.
| Prop | Type | Default |
|---|---|---|
label | stringAccessible label when the children aren't a string. | text content |
Accessibility
- The scroller is a log region and focusable, so it scrolls with the keyboard. Arrow keys, Page Up and Home stop the follow like the wheel does.
- It follows by easing toward a bottom that keeps moving, so streamed text glides instead of jumping a line at a time.
- Call actionsRef.current.scrollToBottom() when someone sends, so their message and the reply come into view even if they'd scrolled up.
- Jump to latest is named with the unread count, and leaves the tab order while it's hidden.
- Under prefers-reduced-motion following and jumping are instant.
- Plain DOM, no virtualisation. 300 rich messages stay smooth when finished messages are memoized, as in the examples.