Marble UI
← Components

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>

PropTypeDefaultDescription
children *requiredReactNode—
autoCollapsebooleantrueOpen while streaming, collapse when done (default).
classNamestring—
durationnumber—Seconds spent; shown once done.
isStreamingboolean—

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 isStreaming turns 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
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