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, 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>
Chat owns scroll behavior; layout comes from
Message and
Reasoning, composed as children. Chat.Item
is the bridge: a neutral wrapper that registers whatever it wraps — a
message, a separator, a tool-call card — with the scroller. Message bodies
are children: markdown rendering, entity chips and embedded cards are
composed by your app — no markdown dependency enters the design system.
Consumers own messages, streaming and sending; everything here is
presentational.
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 replace the default arrow-and-label content.
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
Examples
Attachments
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: "" },
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.