Step Timeline
An ordered list of steps with a "current step" cursor. Steps before the cursor render as done (filled accent marker with check), the cursor step renders as active (hollow accent marker with a subtle pulse), and the rest render as upcoming (hollow muted marker). Lays out vertically or horizontally; connectors between markers can be solid, dotted, or hidden.
- Mise en placeChop, measure, line everything up.
- SearGet a hard crust on each side.
- BraiseLow heat, lid on, 90 minutes.
- PlateRest, slice, sauce, serve.
Customize
Active step
1
Orientation
vertical
Connector
line
Installation
npx shadcn@latest add https://craftbits.dev/r/step-timeline.jsonUsage
import { StepTimeline } from "@craft-bits/core";
<StepTimeline
aria-label="Onboarding progress"
activeStep={1}
steps={[
{ title: "Create account" },
{ title: "Verify email" },
{ title: "Add a profile picture" },
{ title: "Invite teammates" },
]}
/>Horizontal layout with descriptions:
<StepTimeline
orientation="horizontal"
activeStep={2}
steps={[
{ title: "Build", description: "Compile + bundle" },
{ title: "Test", description: "Unit + integration" },
{ title: "Stage", description: "Deploy to staging" },
{ title: "Ship", description: "Promote to prod" },
]}
/>Per-step status (when activeStep doesn't fit your model):
<StepTimeline
steps={[
{ title: "Draft", status: "done" },
{ title: "Review", status: "active" },
{ title: "Sign off", status: "upcoming" },
{ title: "Publish", status: "upcoming" },
]}
/>Understanding the component
- Status derivation. When
activeStepis set, each step's status is computed from its index:< activeStep → done,=== activeStep → active,> activeStep → upcoming. WhenactiveStepis omitted, per-stepstatuswins — which lets you express non-contiguous layouts. - Connector tone follows the leading step. Each
<li>owns its own connector segment, positioned absolutely from below (or right of) the marker. The segment colour tracks the leading step's status — connectors emanating fromdoneoractivesteps pick upborder-cb-accent/60; otherwise they stayborder-cb-border. This yields the canonical "filling pipeline" visual without a separate connector primitive. - Staggered entry. Each step fades in with a 6px slide along the cross-axis. Per-step delay is
index * STAGGER(0.04s), capped at 0.05s by ESLint — the reveal cascades but never feels like a slideshow. - Active pulse. The active marker breathes via
animate={{ scale: [1, 1.05, 1] }}onSPRINGS.smooth, with a 1.2s pause between cycles. The pulse stays inside the[0.95, 1.05]subtle-deformation range. - Reduced motion. When
prefers-reduced-motion: reduceis set, both the stagger and the active pulse short-circuit — markers render in place, no slide, no breathing.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
steps | { title: string; description?: ReactNode; status?: 'done' | 'active' | 'upcoming' }[] | required | Ordered list of steps. |
activeStep | number | — | 0-indexed cursor. Overrides per-step status. |
orientation | 'vertical' | 'horizontal' | 'vertical' | Layout axis. |
connector | 'line' | 'dotted' | 'none' | 'line' | Connector style between markers. |
className | string | — | Merged onto the root <ol> via cn(). |
Accessibility
- Renders as a semantic
<ol>so assistive tech announces it as an ordered list with item count. - The active step carries
aria-current="step". - The component does not auto-label itself. Provide
aria-label(e.g.,"Recipe progress") oraria-labelledbyon the root. - All decorative markup (markers, check icon, connector lines) is
aria-hidden="true"— screen readers read titles and descriptions, not step numbers. prefers-reduced-motion: reducedisables the staggered entry and the active pulse.
Credits
- Extracted from:
terminal-dreams(src/components/cookbook/StepTimeline.tsx). Generalized: stripped the project-specificCookbookStepdata model and the click-to-navigate button behaviour; addedorientation+connectorprops; replaced the "current vs completed" toggle with the canonical three-state (done/active/upcoming) cursor; added stagger + pulse motion tied toSPRINGS.smooth.