--- title: Steps description: A collapsible timeline of a run — reasoning, tool calls, and answered questions as chronological steps. source: steps.tsx --- ## Usage guidelines - **Recursive disclosure tree** — every node is a `Steps.Item` with a `Trigger` and a `Panel`, and panels can hold further items, so timelines nest arbitrarily. - **Status-driven** — each item's `status` (`complete` / `active` / `pending`) flows to its `Icon` and `Label` via context; active items open by default. - **Nesting** — a nested item surfaces `data-nested` for the indent rail; a static row is just an `Icon` + `Label` in a `
`. - **You compose the rows** — the primitive ships the disclosure + status plumbing; row content (icons, tool-call summaries) is yours to render. - **Get started** — see [Installation](/docs/installation) to add the package and copy the component. ## 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`: ```tsx Worked for 3 seconds {/* a static, complete row */}
{checkIcon} Read the request
{/* a nested, expandable row */} {checkIcon} Searched the web Found three relevant sources and skimmed each. {/* an in-progress row — status overrides icon + label styling */}
{spinnerIcon} Writing the answer
``` ## 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: ```tsx Searching the web ``` ## API reference Every part accepts `className`, `style`, and `render` (see [PrimitiveProps](/docs/headless/types)) and emits a bespoke part attribute (`data-`) 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"`. export const itemProps = [ { name: "status", type: "string", default: '"complete"', description: "Node status (commonly complete / active / pending); seeds context for Icon/Label and drives data-status." }, { name: "defaultOpen", type: "boolean", default: "status === active", description: "Uncontrolled initial open state — open by default while active." }, { name: "open", type: "boolean", description: "Controlled open state." }, { name: "onOpenChange", type: "(open: boolean) => void", description: "Fires on toggle." }, ]; export const itemAttrs = [ { attribute: "data-steps-item", description: "The item element." }, { attribute: "data-status", values: "string", description: "The item's status (commonly complete / active / pending)." }, { attribute: "data-nested", values: '"true"', description: "Present when the item is inside another item (indent rail)." }, { attribute: "data-open", description: "Present while open." }, { attribute: "data-closed", description: "Present while closed." }, ]; ### Steps.Trigger Toggles the nearest item. Renders a `