Steps
A collapsible timeline of a run — reasoning, tool calls, and answered questions as chronological steps.
Usage guidelines
- Recursive disclosure tree — every node is a
Steps.Itemwith aTriggerand aPanel, and panels can hold further items, so timelines nest arbitrarily. - Status-driven — each item's
status(complete/active/pending) flows to itsIconandLabelvia context; active items open by default. - Nesting — a nested item surfaces
data-nestedfor the indent rail; a static row is just anIcon+Labelin a<div>. - You compose the rows — the primitive ships the disclosure + status plumbing; row content (icons, tool-call summaries) is yours to render.
- Get started — see Quick start to add the package.
Anatomy
A timeline is a top-level item whose panel holds rows; a row is an Icon +
Label, and a row that expands is itself a nested Steps.Item:
<Steps.Root>
<Steps.Item defaultOpen>
<Steps.Trigger>
<span>Worked for 3 seconds</span>
</Steps.Trigger>
<Steps.Panel>
{/* a static, complete row */}
<div>
<Steps.Icon>{checkIcon}</Steps.Icon>
<Steps.Label>Read the request</Steps.Label>
</div>
{/* a nested, expandable row */}
<Steps.Item defaultOpen>
<Steps.Trigger>
<Steps.Icon>{checkIcon}</Steps.Icon>
<Steps.Label>Searched the web</Steps.Label>
</Steps.Trigger>
<Steps.Panel>Found three relevant sources and skimmed each.</Steps.Panel>
</Steps.Item>
{/* an in-progress row — status overrides icon + label styling */}
<div>
<Steps.Icon status="active">{spinnerIcon}</Steps.Icon>
<Steps.Label status="active">Writing the answer</Steps.Label>
</div>
</Steps.Panel>
</Steps.Item>
</Steps.Root>Status
status is an opaque string — you own the set (commonly "complete",
"active", "pending", but add "error"/"skipped"/anything). Steps.Item
takes a status and publishes it through context; Steps.Icon and
Steps.Label inherit it, or override with their own status. Resolution is
own prop → inherited → "complete". Every status-aware part reflects it as
data-status for styling.
Height transitions
The panel publishes its measured height as --panel-height while an open or
close transition runs, and releases it once the panel settles open. So
height: var(--panel-height) animates from a real number, and then — with the
variable no longer written — becomes invalid at computed-value time and falls
back to auto. That is what lets an open panel track content appearing inside
it, rather than staying pinned to the height it had when it opened.
The demo at the top of this page uses it. Collapse and expand the timeline to see the transition, then expand Searched the web while the timeline is already open — the outer panel grows to fit the detail instead of clipping it.
Two details in that demo are load-bearing. data-starting-style and
data-ending-style clamp the height to 0 on the transitional frames, and they
outrank the base height because a data-attribute variant is more specific.
And [&>*]:shrink-0 guards the measurement: a flex column clamped to height: 0
puts every child under shrink pressure, and a child that collapses to nothing
makes the panel measure itself as 0px.
Keyboard
Each item is a standard disclosure: Steps.Trigger is a real button, so
Tab moves between triggers and Enter/Space toggle the nearest item.
Nested items nest their own triggers — there is no roving focus or composite
widget; the tree is plain sequential tab order.
Accessibility
Disclosure semantics are inherited from the underlying Collapsible:
aria-expanded/aria-controls on the trigger, an id-linked, hidden-managed
panel. The item whose status is "active" additionally carries
aria-current="step" — the same "active" convention defaultOpen already
keys off.
Status itself is invisible to assistive tech by default: Steps.Icon is
aria-hidden and color never announces. Mount Steps.Status inside rows
whose status matters — it renders a visually-hidden span speaking the resolved
status string, and takes children for localized copy:
<Steps.Trigger>
<Steps.Icon />
<Steps.Label>Searching the web</Steps.Label>
<Steps.Status />
</Steps.Trigger>API reference
Every part accepts className, style, and render (see
Styling) and emits a bespoke part attribute (data-<part>) unless noted.
Steps
The timeline root. Renders data-steps. No part-specific props.
Steps.Item
One node of the tree (a disclosure). Renders data-steps-item, plus
aria-current="step" while status is "active".
| Prop | Type | Default |
|---|---|---|
status | string | "complete" |
defaultOpen | boolean | status === active |
open | boolean | — |
onOpenChange | (open: boolean) => void | — |
| Attribute | Values | Description |
|---|---|---|
data-steps-item | — | The item element. |
data-status | string | The item's status (commonly complete / active / pending). |
data-nested | "true" | Present when the item is inside another item (indent rail). |
data-open | — | Present while open. |
data-closed | — | Present while closed. |
Steps.Trigger
Toggles the nearest item. Renders a <button data-steps-trigger>
(aria-expanded, aria-controls). Carries data-open/data-closed for the
chevron. The styled layer groups it as group/steps-trigger so children read
group-data-open/steps-trigger:….
Steps.Panel
The nearest item's disclosure area — lays out the timeline column. Renders
data-steps-panel.
| Prop | Type | Default |
|---|---|---|
keepMounted | boolean | false |
| Attribute | Values | Description |
|---|---|---|
data-steps-panel | — | The panel. |
data-open | — | Present while open. |
data-closed | — | Present while closed. |
data-starting-style | — | Present on the first open frame (enter transition). |
data-ending-style | — | Present while the exit animation runs. |
--panel-height | measured px | The panel's natural height, published only while the open or close transition runs so a height transition has a number to animate from. Deliberately released once the panel settles open, which makes `height: var(--panel-height)` fall back to `auto` so the open panel tracks content that grows inside it. |
Steps.Icon
Status indicator. Renders <span data-steps-icon aria-hidden>.
| Prop | Type | Default |
|---|---|---|
status | string | — |
| Attribute | Values | Description |
|---|---|---|
data-steps-icon | — | The icon element. |
data-status | string | Resolved status, for styling. |
Steps.Label
Row text. Renders <span data-steps-label>.
| Prop | Type | Default |
|---|---|---|
status | string | — |
| Attribute | Values | Description |
|---|---|---|
data-steps-label | — | The label element. |
data-status | string | Resolved status, for styling. |
Steps.Status
Visually-hidden status announcement. Renders <span data-steps-status> with
screen-reader-only styling (overridable via style/className), containing
the resolved status string unless children provide localized copy.
| Prop | Type | Default |
|---|---|---|
status | string | — |
children | ReactNode | the resolved status string |
| Attribute | Description |
|---|---|
data-steps-status | The status element. |