Reasoning / Chain of Thought
Preview: Live
Collapsible "thinking" trace: open while the model reasons, collapses to "Thought for Ns" when finished.
Import
import { Reasoning } from '@marbleui/ui'Props
<Reasoning>
| Prop | Type | Default | Description |
|---|---|---|---|
| children *required | ReactNode | — | |
| autoCollapse | boolean | true | Open while streaming, collapse when done (default). |
| className | string | — | |
| duration | number | — | Seconds spent; shown once done. |
| isStreaming | boolean | — |
Accessibility
Keyboard interaction, focus management and ARIA semantics come from the underlying primitives: React Aria Button · React Aria Disclosure · React Aria DisclosurePanel · React Aria Heading. Follow the primitive's docs for the full keyboard map.
Usage notes
Collapsible trace for models that stream their reasoning. Put it at the top of the assistant ChatMessage, above the MarkdownResponse. For a wait indicator with no trace use ThinkingLoader.
<ChatMessage role="assistant" name="Acme AI">
<Reasoning isStreaming={phase === 'reasoning'} duration={thoughtSeconds} className="mb-3">{trace}</Reasoning>
<MarkdownResponse isStreaming={phase === 'answering'}>{answer}</MarkdownResponse>
</ChatMessage>
- It opens when
isStreamingturns true and collapses when it turns false;autoCollapse={false}leaves it open. Readers can toggle it at any time. - The header reads “Thinking…” while streaming, then “Thought for 7s” from
duration(seconds you measure), or “Reasoning” without one. - The trace is styled as muted plain text with line breaks kept. It isn’t parsed as Markdown.
- The header is a disclosure button inside a heading: Enter or Space toggles it, and screen readers hear whether it’s expanded.
Example source · show· hide
import { useEffect, useState } from 'react'
import { Button } from '../button'
import { Reasoning } from './reasoning'
const trace = `The user wants churn risks. Usage data is weekly, so I'll compare the last 4 weeks to the prior 8.\n\nAccounts with a >30% drop and an upcoming renewal are the strongest signal. Northwind and Initech both qualify; Globex dropped but renewed last month, so it's lower risk.`
function Live() {
const [streaming, setStreaming] = useState(true)
const [n, setN] = useState(0)
useEffect(() => {
if (!streaming) return
if (n >= trace.length) return void setStreaming(false)
const t = setTimeout(() => setN(n + 5), 25)
return () => clearTimeout(t)
}, [n, streaming])
return (
<div className="flex w-[30rem] flex-col gap-3">
<Reasoning isStreaming={streaming} duration={7}>{trace.slice(0, n)}</Reasoning>
{!streaming && <p className="text-sm text-fg">Northwind and Initech are your top churn risks.</p>}
<Button size="sm" variant="outline" className="self-start" onPress={() => { setN(0); setStreaming(true) }}>Replay</Button>
</div>
)
}
export const styles = {
Live,
Done: () => <div className="w-[30rem]"><Reasoning duration={12}>{trace}</Reasoning></div>,
}Source: packages/ui/src/reasoning/reasoning.tsx
Catalog
- Category
- ai-agents
- Priority
- Pro
- Primitive
- composed
- React Aria
- Disclosure
- Base UI
- Collapsible