Chat
The conversation scroller — live-edge following, turn anchoring, prepend preservation and visibility tracking, plus separators and attachments.
1<Flex2 direction="column"3 style={{4 width: 440,5 height: 420,6 border: "0.5px solid var(--rs-color-border-base-primary)",7 borderRadius: "var(--rs-radius-4)",8 }}9>10 <Chat>11 <Chat.Messages>12 <Chat.Separator>Today 1:52 AM</Chat.Separator>13 <Chat.Item messageId="m1">14 <Message align="end">15 <Message.Content>
Anatomy
1import { Chat, Message, PromptInput, Reasoning } from '@raystack/apsara'23<Chat>4 <Chat.Messages>5 <Chat.Separator>Today 1:52 AM</Chat.Separator>6 <Chat.Item messageId="m1" scrollAnchor>7 <Message align="end">8 <Message.Content>9 <Message.Bubble>create a task in design system 2</Message.Bubble>10 </Message.Content>11 </Message>12 </Chat.Item>13 <Chat.Item messageId="m2">14 <Message>15 <Message.Content>16 <Reasoning duration={10}>17 <Reasoning.Trigger />18 <Reasoning.Content>19 <Reasoning.Step label="Gathering ticket updates" />20 </Reasoning.Content>21 </Reasoning>22 {/* consumer-rendered markdown / chips / cards */}23 </Message.Content>24 </Message>25 </Chat.Item>26 <Chat.JumpButton />27 </Chat.Messages>28 <Chat.Composer>29 <PromptInput onSubmit={send}>{/* … */}</PromptInput>30 </Chat.Composer>31</Chat>
Chat owns scroll behavior. Layout comes from [Message](/docs/ai-
elements/message) and Reasoning, composed as
children.
Chat.Item is the bridge — a neutral wrapper that registers whatever it wraps
with the scroller, whether that is a message, a separator, or a tool-call
card.
Message bodies are children, so your app composes markdown rendering, entity chips, and embedded cards. No markdown dependency enters the design system. Consumers own messages, streaming, and sending; everything here is presentational.
Usage
Attachments
Chat.Attachment renders a file alongside a message. Attachments sit inside the message item, so they scroll and anchor with it.
1<Flex direction="column" gap={3} align="start">2 <Chat.Attachment3 title="design-spec.pdf"4 description="1.2 MB"5 onRemove={() => {}}6 />7 <Chat.Attachment8 title="screenshot.png"9 description="Uploading…"10 state="uploading"11 />12 <Chat.Attachment13 title="notes.txt"14 description="Upload failed"15 state="error"
Streaming and turn anchoring
Send a message: it anchors near the top while the reply streams below, and the "↓ Latest" pill returns you to the live edge after scrolling up.
1(function StreamingChat() {2 const [messages, setMessages] = React.useState([3 { id: "m1", align: "end", text: "What changed this week?" },4 { id: "m2", align: "start", text: "Three issues moved to done." },5 ]);6 const timersRef = React.useRef([]);78 React.useEffect(() => () => timersRef.current.forEach(clearTimeout), []);910 const send = (value) => {11 const id = "u" + Date.now();12 setMessages((current) => [13 ...current,14 { id, align: "end", text: value, anchor: true },15 { id: id + "-reply", align: "start", text: "" },
API Reference
Chat
A flex-column container that sizes Chat.Messages against a composer sibling.
Chat.Messages
The smart scroller. It follows streaming output while the reader is at the
bottom, stops following once they scroll up, anchors new scrollAnchor items
near the top of the viewport (ChatGPT/Linear style), and keeps the reading
position stable when older history is prepended.
Prop
Type
Imperative commands are available through actionsRef:
Prop
Type
Chat.Item
The per-item behavior unit. It registers its element with the enclosing
Chat.Messages for visibility tracking and scrollToMessage, marks the turn
anchor, and applies content-visibility: auto containment so offscreen items
skip layout and paint. It wraps anything — it has no opinion about what a
message looks like.
Prop
Type
Chat.JumpButton
The floating "↓ Latest" pill. It appears once the reader leaves the live edge
and scrolls back down on click. Render it as a child of Chat.Messages.
Children are the text slot ("Latest" by default); the leading icon renders
regardless, swap it with leadingIcon or remove it with leadingIcon={null}.
Prop
Type
Chat.Composer
The container under the messages, usually holding a PromptInput. It sits flush against the messages viewport and pads the sides and bottom.
useChatMessages
Hook for components rendered inside Chat.Messages — active-turn indicators,
jump-to-message navigation, custom scroll buttons.
Prop
Type
Chat.Separator
A centered label between hairlines — "Today 1:52 AM". Purely presentational; format dates however your app does.
Chat.Attachment
A presentational attachment preview card for PromptInput.Header or message
bodies. File picking, drag-drop and validation belong to your app.
Prop
Type
Slots
Every rendered part carries a stable data-slot attribute for styling and testing:
| Slot | Element |
|---|---|
chat | Root container |
chat-messages | The scrollable message list |
chat-messages-viewport | Scroll viewport |
chat-messages-content | Content inside the viewport |
chat-messages-spacer | Bottom spacer that keeps the newest turn anchored |
chat-messages-scrollbar | Scrollbar |
chat-item | One message in the list |
chat-separator | Divider between messages |
chat-composer | The input area below the list |
chat-jump-button | "Jump to latest" button |
chat-jump-button-icon | Icon inside that button |
chat-attachment | An attachment card |
chat-attachment-media | Its thumbnail or preview |
chat-attachment-body | Its text column |
chat-attachment-title | Attachment filename |
chat-attachment-description | Attachment size or type |
chat-attachment-remove | Its remove button |
Accessibility
- The
Chat.Messagesviewport hasrole="log"with an accessible label, so new messages are announced politely by screen readers. - The jump button removes itself from the tab order (
tabindex="-1",aria-hidden) while the reader is already at the live edge. - Programmatic scrolling collapses to instant jumps under
prefers-reduced-motion: reduce.