Agent docs

Stepper

A pipeline's stages in one strip; circles, connectors, and an optional click per stage.

The horizontal pipeline strip both apps kept hand-building: a status circle per stage, connectors that turn green as stages complete, the stage name under each circle, and a quiet detail or warning line below it. Promoted under D7 (docs/plan/44-showcase-refinement.md §Rulings) from three near-identical builds: speedway's WorkflowStageChain stepper band, walmart's PipelineStepper and the Jobs-row chain. Columns are equal-width and TOP-aligned deliberately: labels wrap to different heights, and centering the row puts each circle at a different Y so the connectors zigzag (walmart learned this in production; the comment travels here).

When to reach for it#

A job or catalog page that walks a record through named stages; a list row that shows where each job stands; an export flow whose steps a reader may click into. Read-only by default; pass onStep only when the stages are real destinations.

Not for wizards that collect input page by page (that is a form flow, see the SchemaForm mode chooser), and not for two-state toggles.

Contract#

  • steps (required): StepperStep[], each { key, label, status, detail?, warn?, disabledReason? }.
    • status: "pending" | "active" | "complete" | "blocked". active renders the ping (suppressed under reduced motion); blocked renders the paused treatment, never the ping: a job parked in review is not working.
    • detail?: one quiet line (a count, a timestamp). warn? replaces it with the warning ink for the count-that-needs-eyes case ("3 incomplete").
    • disabledReason?: why the step cannot be entered; disables its button and becomes the title, so the refusal is polite rather than silent.
  • aria-label (required): the pipeline's name. A stepper is a landmark.
  • activeKey?: the step whose surface the reader is on; renders aria-current="step" and the underlined label. Independent of status.
  • onStep?(key): makes every step a button. Omit it and no buttons render.
  • size?: "sm" | "md" (default md): sm fits a table row.
  • className: merged last; layout only.

Sanctioned combinations#

  • Inside a Card with a title naming the record; the strip is the card's first body row.
  • size="sm" inside a jobs table's expanded row (the walmart Jobs-row shape).
  • warn on the export stage carrying the incomplete count, with the fuller sentence in disabledReason or the surrounding card.

Banned combinations#

Do not colour labels or connectors through className; the tones are the contract (success for done, primary for active, warning for blocked). Do not use onStep to run mutations; a step click navigates, it never submits. Do not hand-build a stage strip again; this component exists because three copies drifted.

Before you adopt this#

  1. Does the shell or a parent already render this? Speedway's job page renders WorkflowStageChain, whose stepper band this replaces at that app's next deliberate kit upgrade; its note banner and stage list stay local.
  2. Does this app already ship a local implementation? walmart: PipelineStepper.tsx and the Jobs-row chain; speedway: WorkflowStageChain.tsx (the band only). All named in D7.
  3. Does the app's kit pin reach the version this landed in? Stepper ships in the bump after 0.2.2.
  4. Accessible name and keyboard path: pass aria-label (required by type); with onStep every step is a real button, disabled ones expose their reason as title.
  5. Which canon §A rules bind: doc 12's kicker idiom (labels render through Kicker, never hand-typed uppercase); doc 01 §A8 rungs for every wash and edge; doc 11 motion (the ping honours reduced motion).

Travels with#

Card (the strip's usual home), Kicker (renders the labels), StatusPill (a stage list's per-row status, the sibling surface speedway keeps), Table (the sm strip inside an expanded row).

Snippet#

<Stepper
aria-label="Export pipeline"
activeKey={tab}
onStep={(key) => setTab(key)}
steps={[
{ key: "scrape", label: "Scrape", status: "complete", detail: "418 rows" },
{ key: "map", label: "Schema mapping", status: "active" },
{ key: "export", label: "Export", status: "pending", warn: "3 incomplete" },
{ key: "submit", label: "Submit", status: "blocked", disabledReason: "Clear review first" },
]}
/>
@versable-git/ui · reference, canon, and method, read in place