ThinkingTree
Hero example
When to use
Reach for ThinkingTree when the agent is narrating what it is doing right now — checking compliance, reading a prior filing, drafting an artifact. The component answers "show me the API calls underneath the plan" without surfacing raw JSON. Canonical mounts are the marketing hero (under the Incorporation plan card), the in-app assistant chat (wrapped by LiveThinkingPanel), and the per-beat transcript renderer in the mock-founder story pipeline.
Don't reach for ThinkingTree when the user needs to choose a next step — that's a PlanCard (high-level checklist) or a SlidingPill (view switcher). Don't use it for historical audit events either — those carry actor and timestamp metadata and belong in the Timeline namespace.
Anatomy
Four structural parts:
- Header (carded mode only). Chevron + dual-mode counter —
Thinking · step N of Mwhile live,Thought for {duration} · {n} stepsonce final. Clicking the header toggles body collapse. - Dot. A 14 px atom whose data-state attribute carries the recipe — green fill with a check (done), ink fill (active), hairline ring (queued), red fill with an ✕ (failed), dashed ring (skipped).
- Rail. Two 1 px hairline segments per row (upper + lower). Each inherits the state of its adjacent step: the upper segment from the step above, the lower from the current row. Completed segments darken to
--status-green; failed segments darken to--status-red. - Label. Sans text (
--font-sans, Geist). Active rows sit inside a translucent glass pill with a continuous moving-gradient text shimmer; queued and skipped rows use--fg-soft; failed rows use--fg; done rows use--fg.
The row is a <li> inside <ol>. The active row carries aria-current="step".
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| itemsrequired | ThinkingTreeItem[] | — | — |
| className | string | — | — |
| aria-label | string | — | — |
| mode | ThinkingTreeMode | — | When set, the tree renders in carded mode with a header. Omit to render only the row list (default, used by the marketing hero). |
| elapsedMs | number | 0 | Total elapsed time for the turn, in ms. Used by the header. |
| collapsed | boolean | — | Controlled collapse state. |
| onToggle | () => void | — | Toggle handler. If omitted, the tree owns its own collapse state. |
| showTimings | boolean | false | Reveal per-row durations / `…` / `failed` / `skipped` chips. |
| bodyId | string | — | Optional ID hook so the wrapping recipe can wire `aria-controls`. |
| showHeader | boolean | — | Force header off even when `mode` is set. Used by recipes that provide their own header chrome. |
items is the only required prop. Each ThinkingTreeItem is { id, state, label?, verb?, object?, durationMs? }. id is the React key — use the action's stable identifier (a tool-call id, a step index) so re-ordering doesn't remount rows mid-shimmer. Pass label for a single-line step description, or verb + object for the bold-leading-clause + muted-trailing-clause shape used by the marketing hero.
States
| State | Bullet | Label | Rail (below) |
|---|---|---|---|
done | Green fill, white ✓ | Verb --fg · object --fg-muted | --status-green |
active | Ink fill, no glyph | Glass pill + moving shimmer | --ink-8 (default) |
queued | Hairline ring | Both --fg-soft | --ink-8 (default) |
failed | Red fill, white ✕ | Verb --fg · object --fg-muted | --status-red |
skipped | Dashed ring | --fg-soft + strikethrough | --ink-8 (default) |
The legacy state pending is silently mapped to queued for back-compat with the marketing hero — prefer queued in new code.
State transitions animate background, opacity, and border-color over 220 ms — a row promoting from queued → active → done reads as one continuous motion.
Modes
mode is the primary axis:
- bare (default). No header, no card chrome — just the
<ol>of rows. Used by the marketing hero's sliding three-row window where vertical room is tight. - carded (
mode="live" | "final"). Wraps the rows in a hairline card with aTraceHeader.liverendersThinking · step N of M,finalrendersThought for X · N steps. Passcollapsed+onTogglefor controlled collapse, or omit both to let the component own its state.
Density
Density-agnostic — row vertical padding is fixed at 11 px and the type scale is the canonical sans body. To show fewer or more rows, slice the items array at the consumer.
Themes
Tokens consumed
Rails: --ink-8 (default), --status-green, --status-red. Dots: --status-green, --status-red, --fg, --fg-soft, --ink-15. Active pill: --paper-72, --paper-85, --paper-90. Labels: --fg, --fg-muted, --fg-soft. Font: --font-sans.
Accessibility
- Keyboard. None in bare mode. In carded mode the header is a focusable
<button>. - ARIA. Bare mode renders
<ol aria-label>. Carded mode renders<div role="group" aria-label>with the rows as<ol id={controlsId}>and the header carryingaria-controls={controlsId}+aria-expanded. - Screen reader. Rows announce as "Verb Object" or as the single
labelstring; the visual weight contrast and the state glyphs are purely decorative. - Reduced motion. The active row's moving-gradient shimmer collapses to a static
--fgfill. The collapse transition still applies but is imperceptible at its duration. - Forced colors. Dots, rails, and the pill border collapse to
CanvasText; labels inherit system foreground.
Do / Don't
Recipes
- Marketing hero — under the Incorporation plan card, bare mode, sliding three-row window. See
apps/web/app/[locale]/(home)/components/chat-panel.tsx. - Live agent panel —
LiveThinkingPanelwrapsThinkingTreewithmode="live"while streaming,mode="final"once the turn lands. - Mock-founder story — embedded in the per-beat transcript renderer; carded mode, no live shimmer (every row already settled).
Code example
import { ThinkingTree, type ThinkingTreeItem } from "@matter/components";
const items: ThinkingTreeItem[] = [
{ id: "1", label: "Checked compliance obligations", state: "done" },
{ id: "2", label: "Looked up authorized share count", state: "done" },
{ id: "3", label: "Read prior-year filing for par value", state: "failed" },
{ id: "4", label: "Calculating tax under both methods", state: "active" },
{ id: "5", label: "Auto-emailing the filing", state: "skipped" },
{ id: "6", label: "Drafting the franchise tax filing", state: "queued" },
];
export function FilingActivity() {
return (
<ThinkingTree
aria-label="Franchise-tax run"
elapsedMs={4200}
items={items}
mode="live"
/>
);
}Source
packages/components/src/ThinkingTreeSlidingPill
Radix-free tab pill with an animated indicator that slides between items. Single absolute element reads the active item's offsetLeft + width — preserves the v2 liquid-glass settle-spring easing without a third-party state machine.
LiveThinkingPanel
Streamed agent thinking surface — a hairline card holding a vertical step trail. The header reads `Thinking · step N of M` while live and `Thought for X · N steps` once final. The active step's label sits inside a translucent glass pill and runs a continuous moving-gradient text shimmer.