Reasoning

A collapsible reasoning disclosure that tracks streaming state and elapsed duration.

Planning the approach

Check the existing layout, then decide which axis needs centering.

Verifying

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.

Nothing yet.

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.

PropTypeDefault
isStreamingboolean
false
durationnumber
defaultOpenboolean
false
openboolean
onOpenChange(open: boolean) => void
AttributeDescription
data-reasoningThe disclosure root.
data-streamingPresent while isStreaming is true.
data-openPresent while open.
data-closedPresent 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.

PropTypeDefault
childrenReactNode
AttributeDescription
data-reasoning-triggerThe toggle button.
data-openPresent while open.
data-closedPresent 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.

PropTypeDefault
childrenstring | string[]
(required)
keepMountedboolean
false
AttributeValuesDescription
data-reasoning-contentThe panel.
data-openPresent while open.
data-closedPresent while closed.
data-starting-stylePresent on the first open frame (enter transition).
data-ending-stylePresent while the exit animation runs.
--panel-heightmeasured pxThe 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>:

PropTypeDefault
isStreamingboolean
isOpenboolean
setIsOpen(open: boolean) => void
durationnumber | undefined