Reasoning
Collapsible "thinking" section for AI responses — auto-opens while streaming, auto-collapses on completion, with labelled steps.
1<div style={{ width: 420 }}>2 <Reasoning duration={10} defaultOpen>3 <Reasoning.Trigger />4 <Reasoning.Content>5 <Reasoning.Step label="Gathering project context">6 <Text size="small" variant="secondary">7 Looked at recent issues in Design System 2.8 </Text>9 </Reasoning.Step>10 <Reasoning.Step label="Creating the task" />11 </Reasoning.Content>12 </Reasoning>13</div>
Anatomy
1import { Reasoning } from '@raystack/apsara'23<Reasoning streaming={isThinking} duration={10}>4 <Reasoning.Trigger />5 <Reasoning.Content>6 <Reasoning.Step label="Gathering ticket updates">7 {/* indented detail */}8 </Reasoning.Step>9 </Reasoning.Content>10</Reasoning>
Built on Collapsible. Drive streaming from your transport; everything
here is presentational and works anywhere — inside a
Message body, a Chat scroller, or on its own.
Usage
Streaming
Drive streaming from your transport; the trigger label and open state
follow along.
1(function StreamingReasoning() {2 const [streaming, setStreaming] = React.useState(false);3 const timerRef = React.useRef(null);45 React.useEffect(() => () => clearTimeout(timerRef.current), []);67 return (8 <Flex direction="column" gap={4} style={{ width: 420 }}>9 <Button10 size="small"11 variant="outline"12 color="neutral"13 onClick={() => {14 setStreaming(true);15 timerRef.current = setTimeout(() => setStreaming(false), 3000);
Custom trigger label
Replace the default trigger text when "Reasoning" is not the right word for what the model is doing.
1<div style={{ width: 420 }}>2 <Reasoning duration={7}>3 <Reasoning.Trigger>Show the plan</Reasoning.Trigger>4 <Reasoning.Content>5 <Reasoning.Step label="Outline the migration" />6 <Reasoning.Step label="Estimate the blast radius" />7 </Reasoning.Content>8 </Reasoning>9</div>
Controlled
Pass open with onOpenChange to own the panel — needed when reasoning should expand automatically while the model streams and collapse once it finishes.
1(function ControlledReasoning() {2 const [open, setOpen] = React.useState(false);34 return (5 <Flex direction="column" gap={5} style={{ width: "100%" }}>6 <Button size="small" variant="outline" onClick={() => setOpen((o) => !o)}>7 {open ? "Hide reasoning" : "Show reasoning"}8 </Button>9 <Reasoning open={open} onOpenChange={setOpen} duration={4}>10 <Reasoning.Trigger />11 <Reasoning.Content>12 <Reasoning.Step label="Reading the ticket">13 Pulled the last three comments.14 </Reasoning.Step>15 <Reasoning.Step label="Drafting a reply">
API Reference
Reasoning
Prop
Type
The open state auto-follows streaming (open while true, collapsed once it
flips back) until the user toggles the panel manually — from then on it's
theirs.
Reasoning.Trigger
Renders the default status label (shimmering "Thinking…", then "Worked for N seconds") plus a chevron; pass children to replace the label.
Reasoning.Content
The collapsible panel holding the steps.
Reasoning.Step
A labelled row with optional indented detail.
Prop
Type
Slots
Every rendered part carries a stable data-slot attribute for styling and testing:
| Slot | Element |
|---|---|
reasoning | Root container |
reasoning-trigger | The button that expands and collapses the panel |
reasoning-chevron | Chevron inside the trigger |
reasoning-label-streaming | Trigger label while the model is still reasoning |
reasoning-panel | The collapsible panel |
reasoning-body | Content inside the panel |
reasoning-step | One reasoning step |
reasoning-step-label | That step's label |
reasoning-step-body | That step's content |
Accessibility
- Inherits Collapsible semantics: the trigger exposes
aria-expandedand toggles with Enter and Space. - The "Thinking…" shimmer and the panel's height tween pause under
prefers-reduced-motion: reduce.