Reasoning block

Pro

The collapsible thinking block of an AI reply. While the model thinks, a shimmering label counts the seconds and the latest reasoning streams underneath. When it finishes, it folds to "Thought for 7s" and opens to show the full reasoning as calm steps with headings.

Live · keyboard ready
Thinking
Claude Opus 5.5 is thinking. Simulated, nothing was sent to a model.
  • Showing a reasoning model's thinking above its answer in a chat or agent transcript.
  • Letting people audit why an assistant reached a recommendation without crowding the reply.
  • Restoring past messages where the reasoning should stay folded with its original thinking time.
  • Tool or function calls with inputs and outputs: use ToolCall.
  • A multi step agent plan with checkable tasks: use Agent plan.
  • A one line busy indicator with no reasoning to show: use TextShimmer on its own.

Installation

Pro source and install commands unlock with a Pro plan.

example.tsx
import { ReasoningBlock } from "@/components/arc/reasoning-block/reasoning-block"; // While streaming: pass the growing text, then flip status when the answer starts.<ReasoningBlock  reasoning={thinkingText}  status={done ? "done" : "thinking"}  onStop={() => controller.abort()}/> // A finished message from history.<ReasoningBlock reasoning={message.reasoning} status="done" seconds={message.thinkingSeconds} />

API reference

3 parts. The first is the root.

ReasoningBlock

The collapsible thinking block of an AI reply. While the model thinks, a shimmering "Thinking" label counts the seconds and the latest reasoning streams under it in muted text, two lines tall. When it finishes it collapses to "Thought for 7s" with a chevron; opening it shows the full reasoning as steps with headings on a quiet rail. With no props it plays a simulated Claude session on a loop.

PropTypeDefaultDescription
reasoningstring | ReasoningStep[]–The reasoning so far. Pass the growing string while the model streams: blank lines split paragraphs, and a line written as **Heading** or ## Heading starts a new step (a half streamed heading never shows raw asterisks). Or pass { title?, text }[] steps.
status"thinking" | "done" | "stopped" | "error""done" when reasoning is passedthinking shimmers the label, counts seconds and streams the latest line; done collapses to "Thought for 7s"; stopped reads "Stopped after 4s"; error shows an alert icon with "Thinking interrupted" and the reason inside.
secondsnumber–Seconds spent thinking, shown as 7s or 1m 12s. Left out, the block measures from the moment status turns thinking. Done without a measurement reads "Thought process".
model{ name: string; mark?: ReactNode }{ name: "Claude Opus 5.5" }Who is thinking. The name labels the block for assistive technology; the mark is an 18px glyph at the left and defaults to the Claude mark in its brand color.
openboolean–Controls whether the full reasoning shows. Pair with onOpenChange.
defaultOpenbooleanfalseOpens the full reasoning on first render.
onOpenChange(open: boolean) => void–Called when the header is pressed.
onStop() => void–Shows a compact Stop action while thinking. Abort your request, then set status to stopped.
onRetry() => void–Shows Retry in the header when status is error.
errorstring–The reason shown at the end of the reasoning when status is error.
variant"card" | "plain""card"card draws a quiet bordered surface; plain drops the border and padding so the block sits flush inside a message bubble.
classNamestring–Class on the root.

parseReasoning

The parser the block uses, exported for tests or for showing a step count elsewhere. Turns a streamed string into { title?, text }[] steps.

PropTypeDefaultDescription
reasoningRequiredstring | ReasoningStep[] | undefined–Raw streamed reasoning or steps.

ClaudeMark

The Claude mark (Simple Icons, CC0) in its brand color. Pass mono to draw it with currentColor.

PropTypeDefaultDescription
titlestring"Claude"Accessible name.
monobooleanfalseDraws with currentColor instead of #D97757.
Tab
Moves to the header, then to Stop or Retry when shown, then to Copy reasoning inside the open block.
EnterorSpace
Opens or closes the full reasoning from the header.
  • The header is a real button with aria-expanded and aria-controls; its name is the visible label, such as "Thought for 7s".
  • A polite live region announces status changes only (thinking, thought for 7 seconds, stopped, interrupted), never each streamed chunk or each second.
  • The two line live preview is aria-hidden because the same text is in the full reasoning region, which carries aria-busy while streaming.
  • Status is never color alone: errors pair an alert icon with "Thinking interrupted", and the footer pairs a check, square or alert with Done, Stopped or the reason.
  • The no props preview says "Simulated, nothing was sent to a model" to assistive technology.
  • Streaming is chunked: each appended chunk fades in once over 140ms. No blur, slide or per word springs.
  • Stop and Retry overflow a fixed 20px row, so the header is 44px in every state and never shifts when an action appears.
  • The live preview stays anchored to its newest line; when a line wraps, the text glides up with a bounce free spring (0.24s) instead of jumping.
  • Label swaps (Thinking to Thought for 7s) are a single crossfade: out in 100ms, in over 160ms with a 2px rise. The seconds counter rolls in tabular numerals inside a reserved width.
  • Opening, closing and the preview collapsing all animate measured height with one critically damped spring (0.26s) and one opacity fade. Only what streams in while open (a new step, paragraph or chunk) fades on its own, so nothing animates twice.
  • The shimmer and the self-playing loop pause off screen and in background tabs. Reduced motion keeps every end state, drops the shimmer, the glide and the chunk fades, and cuts heights instantly.
  • Fills its container from 320px up; the header never wraps and long labels truncate.
  • The live preview stays two lines at every width, so streaming never changes the block's height.
  • Paragraphs wrap with text-wrap: pretty and break long identifiers instead of scrolling sideways.
  • On touch, the whole header and live preview are one press target of at least 44px.
  • Streamed text is appended as spans that never re-render once on screen; finished messages render plain text.
  • Only the open block mounts its full reasoning, so long transcripts of collapsed blocks stay light.
  • The preview glide is one transform on one element driven by a ResizeObserver, with no layout animation on text.

Notes for AI

Give your coding assistant the Markdown reference instead of screenshots.

  • Use it above the answer in an assistant message whenever the model streams reasoning (extended thinking, reasoning summaries). Feed it the raw thinking text and switch status to done as soon as answer tokens start.
  • Do not pass seconds while streaming unless your server measures it; the block counts by itself and freezes the value when status leaves thinking. Store that number with the message so history shows the same "Thought for 7s".
  • Keep reasoning out of the answer text. For tool calls made during reasoning, render ToolCall rows after the block instead of inside it.
  • Use variant="plain" inside a message bubble that already has its own surface, so you never nest cards.
  • Swap the mark for another provider by passing model={{ name, mark }} with an 18px SVG.

The full library index for assistants is at /llms.txt.