Dev view
Stepper
A sequential multi-step indicator that visualises progress through an ordered process — checkout flows, signup wizards, multi-step forms, onboarding sequences. Distinct from Tabs (which switches between non-sequential parallel views) by enforcing order: steps complete in sequence, prior steps may be revisited but later steps are gated until prior steps validate. Distinct from Accordion (independent collapsible regions) by the strict sequence and the visible progress-through-N-steps state.
When to use
Use
For sequential multi-step processes where the order matters and progress through-the-flow is meaningful — checkout flows, signup wizards, multi-step forms, onboarding sequences, document signing flows. The user must (or typically does) complete the steps in sequence; their progress is visible at all times.
Avoid
For parallel views of the same content — that is `Tabs`. For independent collapsible regions — that is `Accordion`. For navigation between top-level pages — that is `SidebarNav`. For sequences exceeding ~7 steps — redesign as a wizard with phase grouping or as a multi-page flow.
Versus related
- tabs
`Tabs` is parallel — peer panels switchable in any order. `Stepper` is sequential — ordered steps that complete in sequence. The semantic distinction drives the role (`role="tablist"` with `aria-selected` vs `<ol>` with `aria-current="step"`). Use Tabs for non-sequential views; Stepper for ordered progress.
- accordion
`Accordion` is a list of independent collapsible regions; `Stepper` is a sequential progression with gating between steps. Accordion items are peers with no ordering; Stepper steps are ordered and gated. They look superficially similar (vertical list of expandable items) but the semantic distinction is clear in canonical usage.
- sidebar-nav
`SidebarNav` navigates between independent pages with their own URLs; `Stepper` shows progress within a single multi-step flow. SidebarNav is persistent global navigation; Stepper is contextual to the current flow.
Code anatomy
| Slot | Code slot | Semantic |
|---|---|---|
root | root | progress-indicator |
step | step | listitem |
step-indicator | step-indicator | presentational |
step-label | step-label | text |
step-description | step-description | text |
connector | connector | presentational |
Variants, properties, states
Variants
Structurally different versions of the component.
standarddotvertical Properties
The same component, parameterised.
| Property | Type |
|---|---|
nonLinear | boolean |
hasDescriptions | boolean |
size | sm | md | lg |
States
Browser/user-driven (interactive) vs. app-driven (data).
| Kind | States |
|---|---|
interactive | hoverfocus-visibledisabled |
data | pendingactivecompletederror |
State transitions
| From | To | Trigger |
|---|---|---|
pending | active | User progresses to this step — by completing the prior step (linear mode) or by clicking the step directly (`nonLinear: true`). The previously- active step transitions out of active; this step becomes active and `aria-current="step"`. |
active | completed | User completes the current step (validation passes, "Next" activated). The step transitions out of active; the next step becomes active. The completed step's indicator changes to a checkmark. |
active | error | User attempts to advance from this step but validation fails. Step gains error state; the step's indicator changes to error glyph; an inline error message describes the failure. Stays as the active step — the user must resolve the error before advancing. |
error | active | User resolves the validation error. State returns to active; error indicator clears. |
completed | active | User navigates back to a previous step (per `nonLinear: true` or the canonical "user may revisit prior steps" pattern). Previously-active step transitions to its appropriate state (typically pending if not yet completed, or completed if revisiting after completion). |
Cross-framework expression
| Framework | Structure mechanism | Variant mechanism |
|---|---|---|
| Web Components | A `<ui-stepper>` host with `<ui-step>` children; `<ol>` element internally; named slots for label and description per step | attributes (`variant="vertical"`, `non-linear`, `size="md"`); per-step `state` attribute (pending / active / completed / error); `data-current` for active step |
| React | Compound components (`<Stepper>` plus `<Stepper.Step>` plus `<Stepper.StepLabel>` plus `<Stepper.StepDescription>`); React Aria does not ship a Stepper primitive but `useListBox` plus custom step semantics works; Material UI ships `<Stepper>` with `<Step>` and `<StepLabel>` matching the canonical structure | props with class-variance-authority for variant / size; controlled or uncontrolled `activeStep` index; per-step `completed`, `error` state props |
| Angular (signals) | Angular Material `MatStepper` plus `MatStep`; or Angular CDK Stepper for headless composition; signal-based active-step state | input<'standard' | 'dot' | 'vertical'>(); `[nonLinear]`, `[size]` host bindings; per-step `[state]` input |
| Vue | Vuetify `<v-stepper>` plus `<v-stepper-item>`; or custom composition with `<ol>` plus per-step components | defineProps with literal-union types; `:variant`, `:non-linear` props |
Events
stepActivatestepCompletestepError
Performance thresholds
maxStepsBeforeRedesignstep-count≥7stepsAbove ~7 steps, the cognitive load of remembering progress through the sequence overwhelms the stepper pattern. Long sequences should be redesigned as a wizard with section grouping (group steps into logical phases), or split into multi-page flows where each page has its own short stepper. The threshold matches the "7 plus or minus 2" working- memory canonical guideline (Miller's law).
Accessibility
| Slot | Accessibility hint | |
|---|---|---|
root | Apply `role="list"` (or `<ol>` for the canonical ordered-list semantic) plus `aria-label="Progress"` or "Steps". Each step is a `<li>` with role inferred or explicit. The current step also carries `aria-current="step"` to announce position to SR users. | |
step | Each step is a `<li>` carrying state via `aria-current="step"` (current), `aria-disabled` (disabled future steps in linear mode), or no attribute (pending / completed). For interactive steppers (where the user may click prior steps to revisit), the step is wrapped in a `<button>` carrying the accessible name from the label. | |
step-indicator | Decorative — the step's accessible state (`aria-current`, `aria-disabled`) plus visually- hidden text ("Step 2 of 5, complete") communicates state. The number / checkmark / error glyph is visual reinforcement only. SR users do not rely on the indicator glyph. | |
step-label | Plain text. The step's accessible name. For interactive steps wrapped in `<button>`, the label is the button's text content. For dot variant (label visually-hidden), the label remains in the DOM via sr-only — never `display: none`. | |
step-description | Plain text. SR users hear it after the label when navigating into the step. For interactive steps, the description is referenced via `aria-describedby` on the step's button to compose into the announcement. | |
connector | Decorative — `aria-hidden="true"`. Sequence is communicated through DOM order plus the steps' position cues (`aria-current`, `aria-disabled`, visually-hidden "step N of M" text); the line is visual reinforcement. |
Accessibility acceptance
Keyboard walk
| Keys | Expected |
|---|---|
Tab | For interactive steppers, focus enters the current step's button; subsequent Tab moves to the next focusable in the document (or to the "Next" button if present below the stepper). Disabled future steps in linear mode are skipped from tab order. |
ArrowLeft / ArrowRight (focus on a step, horizontal orientation) | For non-linear steppers, moves focus to the previous / next step via roving tabindex. Activation does NOT auto-occur — Enter / Space activates. Linear steppers do not bind ArrowKeys on steps because future steps are disabled. |
ArrowUp / ArrowDown (vertical orientation) | Same as ArrowLeft / Right for vertical layouts. |
Enter or Space (focus on step) | Activates the step — navigates to it (non-linear) or is a no-op for already-active step. For completed steps in linear mode, activation navigates back to revisit (canonical "may revisit prior steps" allowance). |
Home / End (focus on step) | Moves focus to first / last step in non-linear mode. In linear mode, Home is the first completed or active step; End is the active step (future steps not focusable). |
Screen-reader announcements
| Trigger | Expected |
|---|---|
| Focus enters current step | SR announces "Step <N> of <M>, <label>, current step, button" (or "link" if stepper-as-nav). The position cue comes from visually-hidden text or `aria-describedby`; the current state from `aria-current="step"`; the label from text content. |
| Step transitions to completed | SR announces the new state via the next announcement on focus change; the visual checkmark is decorative. For consumers using a polite live region to announce step completion, the announcement reads "Step 2 of 5 complete; Step 3 of 5, Payment, now active". |
| Step transitions to error | SR announces the error state via the polite live region: "Error in Step 2 of 5, Shipping: <error message>". Focus moves to the first invalid field within the step's content. `aria-invalid="true"` on the field. |
| Stepper completes (final step → completed) | SR announces completion via the live region: "Process complete." Focus moves to the next page or confirmation surface; the stepper itself remains as a record of the completed sequence. |
axe-core rules to assert
aria-allowed-rolearia-required-attraria-currentaria-valid-attr-valuecolor-contrastlistitemrole-img-alt
Common mistakes
#stepper-no-aria-current
Active step missing `aria-current`
Visual active step is shown via background colour or indicator emphasis; `aria-current="step"` is missing. SR users hear no signal of which step is active; they cannot orient to the position in the sequence.
The active step carries `aria-current="step"` (the canonical APG-recommended value for sequence steps). SR announces "current step" before the step's label. Update on every step transition.
#stepper-skip-validation
Linear stepper allows skipping required steps
User clicks step 4 in linear mode while step 2 is incomplete; the click navigates them there regardless. Required validation per step is bypassed; the underlying flow breaks downstream.
In linear mode (`nonLinear: false`), future steps are `aria-disabled` until prior steps complete. Click is a no-op (or shows a "complete prior steps first" message). The "Next" button is the canonical forward-navigation; clicking step indicators directly is allowed for prior steps but not future steps.
#stepper-step-not-button
Interactive step not a button
Implementation makes step indicators clickable via onClick on a `<div>`. SR users do not get button semantics; keyboard users cannot activate via Enter / Space; focus management is bespoke.
For interactive steps (any step the user may navigate to via click), wrap in `<button>` (or `<a>` for stepper-as-navigation patterns where each step has its own URL). Standard button semantics apply: focus, Enter / Space activation, accessible name from text content.
#stepper-no-keyboard-nav
Steps reachable only via mouse
Implementation lacks keyboard support — Tab cycles through every step but ArrowKeys do not navigate between steps; or the steps are not focusable at all.
Steps are reachable via Tab (when interactive). For non-linear steppers, ArrowLeft / ArrowRight (or ArrowUp / ArrowDown for vertical orientation) move focus between steps via roving tabindex; Enter / Space activates. Linear steppers may use Tab-only navigation since future steps are disabled.
#stepper-progress-cues-decorative-only
Progress communicated only by visual indicators
Visual indicators (numbered circles, checkmarks, coloured states) communicate progress. SR users hear no progress percentage or "step N of M" composition. The visual story is rich; the accessible story is empty.
Compose visually-hidden "Step N of M" text into each step's accessible name. The stepper's root may additionally carry `aria-valuenow` / `aria-valuemin` / `aria-valuemax` if treating the stepper as a progress indicator (rare; usually the per-step announcement is sufficient).
Figma↔Code mismatches
- 01 Figma
Stepper drawn as horizontal Tabs
CodeA list of steps with `aria-current="step"` plus order semantics
ConsequenceDesigners may use Tabs visually for sequential flow. Implementations following the design ship `role="tablist"` with `aria-selected` instead of `aria-current="step"`. SR users hear "tab" with no sense of sequence; the "X of N steps" announcement is missing; users navigate between tabs and bypass step ordering.
CorrectDistinguish at the canonical level: Stepper is sequential and ordered; Tabs is parallel and switchable. The semantic distinction drives the role (`<ol>` / `role="list"` vs `role="tablist"`) and the state (`aria-current="step"` vs `aria-selected`). Use Tabs for parallel views; Stepper for sequential progression.
- 02 Figma
Step indicator drawn but no accessibility for "step N of M"
CodeEach step carries visually-hidden "Step 2 of 5" text in addition to the indicator number
ConsequenceDesigners compose mocks with the indicator showing "2" inside a circle. Implementations following the design ship the visual indicator only; SR users hear the indicator number announced as a digit but without context — they do not know how many steps remain or which step is current.
CorrectPair the visual indicator with visually-hidden text composing the position into the step's accessible name: "Step 2 of 5, Shipping, current". The visually-hidden text composes via `aria-describedby` or directly inside the step container. The indicator number itself can be `aria-hidden`.
- 03 Figma
Sequential and non-linear stepper drawn as the same component
CodeA `nonLinear: boolean` property that gates whether the user may click prior or future steps
ConsequenceDesigners do not distinguish linear (one-direction- only) from non-linear (jump-anywhere). Implementations following the design may ship the wrong mode; users expecting to jump to a future step in linear mode get blocked silently; users expecting linear sequence in non-linear mode skip required steps and the underlying flow breaks downstream.
CorrectDocument `nonLinear` as a first-class property. Linear (default false): only completed and current steps are reachable; future steps are `aria-disabled`. Non-linear (true): all steps reachable; user navigates freely. Visual treatment communicates the mode (disabled-future- steps in linear mode are clearly muted and unclickable).
- 04 Figma
Connectors drawn as a single uniform line
CodeConnectors with style per adjacent-step-state pair (solid for completed-to-completed, muted for pending)
ConsequenceDesigners may use one connector style throughout for visual simplicity. Implementations following the design ship a single uniform line; users cannot distinguish completed sequence from pending sequence visually. The progress-through-N-steps story is lost.
CorrectConnector style varies based on the states of its flanking steps. Solid line for completed-to-completed (or completed-to-active); muted dashed or thinner line for pending-to-pending. Visually communicates progress-flow direction. Decorative — the canonical semantic still lives in `aria-current` and step states.