Reasoning Indicator React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
A React reasoning indicator for AI chat: a shimmering live timer while the model thinks, then a quiet "Thought for 12 s" row to expand.
While the model thinks, the label shimmers, a timer counts up in tenths and then whole seconds, and the last two lines of the thought slide up under it. Tap or click the row or the peek (or Tab to the row and press Enter or Space) to open the full reasoning: it follows the newest thought while it streams, and scrolling up inside it lets go. When thinking ends the row settles on "Thought for 12 s" and closes again if it was open from the start; if you opened it yourself it stays open. On a phone the row keeps a 44px hit area and the open body never grows past 40% of the screen.
Reasoning Indicator is a Pro React component in the React AI components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add reasoning-indicator, together with Markdown, useStickToBottom, AI Chat, useControllableState, useLocale and 3 more and the npm packages motion and lucide-react.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add reasoning-indicatorReasoning Indicator is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import ReasoningIndicator from @/components/ui/reasoning-indicator.
import { ReasoningIndicator } from "@/components/ui/reasoning-indicator"
const labels = {
thinking: "Working on it",
thought: (duration: string) => `Worked for ${duration}`,
}
export function Thinking({ text, streaming, startedAt, endedAt }: { text: string; streaming: boolean; startedAt: number; endedAt?: number }) {
return (
<ReasoningIndicator
content={text}
streaming={streaming}
startedAt={startedAt}
endedAt={endedAt}
labels={labels}
/>
)
}Props
contentdefault""stringThe reasoning as markdown. Empty with no children: the row is plain text, with nothing to open.
childrenReactNodeReplaces the markdown body, e.g. a list of searched sites or steps.
labeldefault""ReactNodeReplaces the default label in any state, e.g. "Searching the web" while a phase runs, or labels.stopped(duration) for a stopped message. Leave empty for thinking / thought.
icondefault"<Brain />"ReactNodeThe 16px icon before the label (a Globe for a web search).
showIcondefaulttruebooleanShows the icon before the label.
showDurationdefaulttruebooleanThe live timer while thinking and the time in the done label. Off: the done label reads labels.thoughtBriefly.
variantdefault"ghost""ghost" | "soft" | "outline"ghost is a flush text row for messages; soft and outline wrap the row and the body in a rounded box for standalone use.
sizedefault"md""sm" | "md"The row is 28 / 32px tall with 13 / 14px text. The body is small markdown either way.
tonedefault"neutral""neutral" | "brand" | "success" | "warning" | "danger" | "info"Colors the rail beside the body and the icon while thinking. Neutral uses the foreground.
colordefault""stringAny CSS color. Replaces the tone.
shimmerdefaulttruebooleanThe moving highlight on the label and the icon pulse while thinking. Off (and under reduced motion) the label stays still and muted.
maxHeightdefault320numberPixels of open body before it scrolls inside. On a phone it is also capped at 40dvh. 0 lets it grow.
streamingdefaultfalsebooleanThinking now: shimmer, live timer, the two-line peek and the body following new text.
defaultOpendefaultfalsebooleanStarts open (uncontrolled).
openbooleanControls the disclosure. Pair with onOpenChange.
onOpenChange(open: boolean) => voidCalled when the person toggles it or autoCollapse closes it.
startedAtDate | string | numberWhen thinking began. While streaming the timer counts from here; a second phase continues from it.
endedAtDate | string | numberWhen thinking ended. Before startedAt counts as 0. Without timing data the done label says labels.thoughtBriefly.
durationMsdefault0numberThe final duration, when known; wins over startedAt / endedAt once done. Unset (Measured in the playground) uses the measured run.
autoCollapsedefaulttruebooleanWhen thinking ends, closes again if it was open when the stream started and nobody touched it. A row the person opened stays open.
peekdefaulttruebooleanShows the last two lines of the thought under the label while it streams and the row is closed.
localedefault""stringFormats the duration ("12 s", "2 min. 5 s"). Empty uses UIProvider, then the browser.
nowDate | numberPins the clock for screenshots and tests. By default a shared ticker runs only while streaming.
labelsPartial<ReasoningIndicatorLabels>thinking "Thinking", thought (duration) => "Thought for 12 s", thoughtBriefly "Thought for a few seconds", stopped (duration) => "Stopped after 3 s", show "Show reasoning", hide "Hide reasoning" (sr-only on the trigger). Pass only the keys you translate.
classNamesPartial<Record<"trigger" | "icon" | "label" | "duration" | "peek" | "content" | "body", string>>Extra classes for the inner parts.
classNamestringExtra classes for the root, merged last.
Parts
DEFAULT_REASONING_INDICATOR_LABELS
constantThe English copy: thinking "Thinking", thought(duration) "Thought for 12 s", thoughtBriefly, stopped(duration), show "Show reasoning" and hide "Hide reasoning".
Examples
In a message
Above a streaming answer; autoCollapse folds it when the answer starts.
Named phases
"Searching the web" with a Globe, then "Reading 3 sources", then done.
Standalone box
variant soft with maxHeight 200 and a long reasoning that scrolls.
Stopped
"Stopped after 3 s" for a message the person stopped.
Sizes and tone
sm and md, the info tone, thinking and done.
Outline, brand
The default labels with a duration over two minutes.
Dependencies
Files
- components/ui/reasoning-indicator.tsx
FAQ
Is the React Reasoning Indicator component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Reasoning Indicator?
Run npx wingo-ui@latest add reasoning-indicator. It also adds the Wingo UI items it builds on (Markdown, useStickToBottom, AI Chat, useControllableState, useLocale and 3 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/reasoning-indicator.
How does Reasoning Indicator behave on phones?
On a phone the row keeps a 44px hit area and the open body never grows past 40% of the screen.
See also
Used in
More React AI components
Articles using this component
- AI Agent Tool Call UI: Steps, Approvals and Errors
Building UI with AI agents11 min read
- React AI Chat UI: Streaming, Auto-Scroll and Mobile
Building UI with AI agents10 min read