---
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 webFound 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 `