Stepper

Shows progress through a sequence of steps. Step state is derived from activeStep — everything before it is complete, everything after is upcoming — across four layouts, four connector styles, three marker sizes and three label placements. There is deliberately no per-item state prop: one number driving every step is the whole reason four of them stay consistent.

tsx
import { Stepper, StepperItem, StepperCounter } from "@pestoui/core"

Horizontal

The default. Complete markers and the connectors behind them take --selected, the visible brand green the system uses for anything checked, active or progressing — never --info, which is reserved for informational UI. The current step keeps its number instead of a check.

  1. ConnectPick a data source
  2. ConfigureSet the agent's goal
  3. ReviewCheck the first run
  4. PublishShip to the workspace

Vertical

For setup checklists and side panels, where each step carries a longer description.

  1. Workspace createdteam.example.com
  2. Members invited4 invites sent
  3. Connect appsChat, docs and code
  4. Launch first agentRuns on your schedule

Dots

A compact indicator for wizards and carousels. Titles are still required — they stay in the accessibility tree even though only the dot is drawn.

  1. Connect
  2. Configure
  3. Review
  4. Publish

Bar

Rails instead of markers, for a long-running job where the question is how far, not which step. Each step owns its own rail and the gap between them is the seam — contiguous segments would read as one bar with stripes painted on. progress fills the current step's rail partially; every other step is full or empty, so a partial value anywhere else is ignored rather than quietly believed.

  1. Upload4 files
  2. ExtractPage 12 of 26
  3. EmbedWaiting
  4. IndexWaiting

Connectors

line, dashed, progress and none, top to bottom. dashed is drawn as a border rather than a background, which is what keeps it honouring --border instead of needing an arbitrary gradient. progress looks identical at rest and grows its fill on the layout spring when a step completes, so it reads as travel rather than as a colour swap — see it move in the last demo on this page. none keeps its slot, so step widths stay equal whether or not anything is drawn between them.

  1. Draft
  2. Review
  3. Approve
  4. Publish
  1. Draft
  2. Review
  3. Approve
  4. Publish
  1. Draft
  2. Review
  3. Approve
  4. Publish
  1. Draft
  2. Review
  3. Approve
  4. Publish

Marker sizes

sm, default and lg — 24, 32 and 40px markers, each pinning its own type size so the number stays optically centred. The label offset moves with the diameter too, which is why a vertical stack stays level at every size. Ignored by dots, whose pip is fixed, and by bar, which has no marker at all.

  1. Connect
  2. Configure
  3. Review
  1. Connect
  2. Configure
  3. Review
  1. Connect
  2. Configure
  3. Review

Icons & the error status

icon replaces the number inside the marker and is hidden from assistive tech — title is what names the step, so the glyph is free to be decorative. status="error" decorates the derived state rather than replacing it: the failed step is still the step you are on, so it keeps the ringed treatment and only changes tone. Colour alone cannot report a failure, so the accessible name is prefixed with “Error:” and the marker shakes once on arrival.

  1. ConnectSource linked
  2. ConfigureGoal set
  3. Error: VerifyToken rejected
  4. PublishNot started

Label placement & the counter

labels defaults to below for horizontal and bar, end for vertical, none for dots — and the nonsensical pairings are clamped rather than rendered, since a vertical stack has no room under a marker. With labels="end" every step is forced onto the same basis so titles wrap instead of shoving their neighbours along the row. StepperCounter reads the count off the same context the list does, so “Step 2 of 3” cannot drift from the number of children; place it inside Stepper and it is lifted out of the list and rendered after it. It stays silent for screen readers while labels are on screen, because aria-current has already said which step is current, and only speaks when it is the sole indication.

  1. ConnectPick a source
  2. ConfigureSet the goal
  3. PublishShip it
  1. Connect
  2. Configure
  3. Publish

Step 2 of 3

Clickable steps

onSelect turns the marker into a real button with a hit area enlarged past its own box, and hands back the index it was given. The li then drops its own aria-current so the step is not announced as current twice. Click along the row to watch the progress connector travel rather than snap.

  1. ConnectPick a data source
  2. ConfigureSet the agent's goal
  3. ReviewCheck the first run
  4. PublishShip to the workspace

Props

PropTypeDefaultDescription
variant"horizontal" | "vertical" | "dots" | "bar""horizontal"Presentation. dots hides the labels visually but keeps them for screen readers; bar swaps markers for one rail per step.
activeStepnumber0Zero-based index of the current step. Earlier steps render complete, later ones upcoming.
size"sm" | "default" | "lg""default"Marker diameter — 24 / 32 / 40px, each with its own type size and label offset. Ignored by dots (fixed pip) and bar (no marker).
connector"line" | "dashed" | "progress" | "none""line"What is drawn between markers. none keeps its slot so step widths stay equal. Ignored by dots, which is unconnected by design and therefore defaults to none.
labels"below" | "end" | "none"—Where the title and description sit. Defaults to below for horizontal and bar, end for vertical, none for dots; impossible pairings are clamped rather than rendered. none keeps the text for assistive tech.
StepperItem titleReact.ReactNode—Step label. Required — it also names the marker for assistive tech.
StepperItem descriptionReact.ReactNode—Optional secondary line under the title. Not rendered visually in the dots variant.
StepperItem iconReact.ReactNode—Replaces the step number inside the marker. Hidden from assistive tech — title is what names the step.
StepperItem status"error"—Decorates the derived state, never replaces it: the step keeps the current treatment, retoned to destructive, prefixes its accessible name with “Error:” and shakes once on arrival.
StepperItem progressnumber—0–100. variant="bar" on the current step only — a partial fill of its rail. Every other step is full or empty.
StepperItem onSelect(index: number) => void—Turns the marker into a button with an enlarged hit area and reports the clicked step's index. In bar the whole rail-and-label column becomes the target, since a ring around a 4px rail is unreadable.
StepperCounterReact.ComponentProps<"p">—Renders “Step N of M” from the same context the list uses, so it cannot drift from the number of children. Place it inside Stepper; it is lifted out of the list and rendered after it. Pass children to override the text.

Pesto UI · 117 documented components