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.
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. |