Reasoning
A collapsible reasoning disclosure that tracks streaming state and elapsed duration.
Check the existing layout, then decide which axis needs centering.
Confirm the element centers both horizontally and vertically.
Usage guidelines
- Chain-of-thought disclosure — a collapsible block for a model's thinking.
- Live label — shimmers "Thinking…" while streaming and settles to "Thought for Ns" when it stops; the duration is tracked for you.
- Sectioned content — bold
**Header**lines split the text into labelled sections, rendered as markdown. - State via
useReasoning— read streaming, open, and duration from anywhere inside. - Get started — see Quick start to add the package.
Anatomy
<Reasoning.Root isStreaming={isStreaming}>
<Reasoning.Trigger />
<Reasoning.Content>{reasoningText}</Reasoning.Content>
</Reasoning.Root>Driven from a streaming message, passing the reasoning parts' text:
<Reasoning.Root isStreaming={isLast && isStreaming} defaultOpen={false}>
<Reasoning.Trigger label={headers} />
<Reasoning.Content>{texts}</Reasoning.Content>
</Reasoning.Root>Examples
Tracking the streaming state
isStreaming is the only input. While it is true the root carries
data-streaming and aria-busy; the moment it flips false the elapsed time is
captured and handed back as duration, so the trigger's label changes without
you timing anything.
The panel animates through --panel-height, which is published only while a
transition runs and released once it settles open. That release is what lets an
open panel keep growing as sections stream into it.
API reference
Every part accepts className, style, and render (see
Styling) and emits a bespoke part attribute (data-<part>) unless noted.
Reasoning
The disclosure root. Tracks how long isStreaming stayed true and hands the
elapsed seconds back through useReasoning, so a label can change without the
consumer timing anything. Renders a <div> element.
| Prop | Type | Default |
|---|---|---|
isStreaming | boolean | false |
duration | number | — |
defaultOpen | boolean | false |
open | boolean | — |
onOpenChange | (open: boolean) => void | — |
| Attribute | Description |
|---|---|
data-reasoning | The disclosure root. |
data-streaming | Present while isStreaming is true. |
data-open | Present while open. |
data-closed | Present while closed. |
Reasoning.Trigger
The toggle. Ships no copy: supply the label as children, and read isStreaming
and duration from useReasoning if it should change while the model is
thinking. Renders a <button> element.
| Prop | Type | Default |
|---|---|---|
children | ReactNode | — |
| Attribute | Description |
|---|---|
data-reasoning-trigger | The toggle button. |
data-open | Present while open. |
data-closed | Present while closed. |
Reasoning.Content
The collapsible panel. Publishes its measured height while a transition runs
and releases it once settled open, so a panel that is open keeps growing as text
streams in. Renders a <div> element.
| Prop | Type | Default |
|---|---|---|
children | string | string[] | (required) |
keepMounted | boolean | false |
| Attribute | Values | Description |
|---|---|---|
data-reasoning-content | — | The panel. |
data-open | — | Present while open. |
data-closed | — | Present while closed. |
data-starting-style | — | Present on the first open frame (enter transition). |
data-ending-style | — | Present while the exit animation runs. |
--panel-height | measured px | The content's natural height, published only while the open or close transition runs so a height transition has a number to animate from. Deliberately released once it settles open, which makes `height: var(--panel-height)` fall back to `auto` so the open panel tracks reasoning text as it streams in. |
useReasoning
Read the disclosure state from anywhere inside <Reasoning.Root>:
| Prop | Type | Default |
|---|---|---|
isStreaming | boolean | — |
isOpen | boolean | — |
setIsOpen | (open: boolean) => void | — |
duration | number | undefined | — |