Thinking steps Pro

The trace an agent leaves while it works. The step in progress shimmers, finished steps draw a check and fill the rail down to the next one, and new steps slide in. When the work is done, the header sums it up and the steps fold away behind it, one click from coming back.

Pro preview

Installation

Terminalbash
npx @neelshha/ui@latest login
npx @neelshha/ui@latest add thinking-steps

Usage

page.tsxtsx
import { ThinkingSteps } from "@/components/ui/thinking-steps";

<ThinkingSteps
  done={finished}
  steps={[
    { id: "read", label: "Reading the question", status: "done" },
    {
      id: "search",
      label: "Searching 6 sources",
      detail: "spring physics",
      status: "active",
    },
    { id: "draft", label: "Drafting the answer", status: "pending" },
  ]}
/>

Keep ids stable as statuses change. Steps that are on screen at mount don't animate, so a trace loaded from history appears as it was.

Then the answer

Start folded and let the steps open while the work runs. When it's done they fold back, and the answer streams in under them.

Pro previewPro preview
page.tsxtsx
<ThinkingSteps steps={steps} done={thought} defaultOpen={false} />
{thought ? (
  <StreamingText streaming={streaming}>{answer}</StreamingText>
) : null}

Keyboard

The header is a button with aria-expanded: Enter or Space folds and unfolds the steps. Folded steps are inert, out of the tab order and the accessibility tree. Each step is read with its status first, like "Done: Reading the question".

Source

API

Other props go to the outer div. It's sized in em, so a font size on it scales the whole trace. With reduced motion, steps and checks appear at once and the step in progress is marked in ink instead of shimmering.

PropTypeDefaultDescription
stepsThinkingStep[]Each is { id, label, detail?, status }, with status pending, active, done, or error. Steps added later slide in.
donebooleanfalseTrue once the work is finished. The header stops shimmering and sums up.
secondsnumberFor the header. Leave it out and it times itself until done.
labelReactNode"Thinking"The header while working.
summary(seconds: number) => ReactNode"Thought for {n}s"The header once done.
open / defaultOpenbooleantrueControlled or uncontrolled. onOpenChange fires with the next state.
collapseOnDonebooleantrueFolds the steps away once done, and opens them when work starts again.