Navigation
Stepper
Stepper shows progress through a fixed, known-length, ordered sequence of named steps — a compound Stepper + Step API built against the verified live Navigation/Step Item component set.
Composed from Step children — state (Completed/Current/Upcoming) derived from position, not a component variant
betaReact AvailableFigma AvailableDocs Partial
- React last updated
- 2026-09-14
- Documentation last updated
- 2026-09-14
- Accessibility target
- WCAG 2.2 AA (target)
- Version
- 0.1.0-beta
Known open questions
- Narrow-viewport and long (5+ step) sequence behavior is not defined by the verified Figma contract — Figma's own composed example is a fixed-width demo. Deferred pending real evidence (Reference App).
- Vertical orientation is not part of the verified contract — no orientation axis exists in Figma at all.
- Whether onStepClick should be provided by default (fully interactive) versus omitted (read-only) is an app-level product decision Figma does not resolve.
Live preview
Fixed horizontal sequence of named steps. Completed/Current/Upcoming are derived from currentStep — Stepper never owns navigation or routing.
Interactive (onStepClick advances Completed/Current steps)
- Payment
- Complete
currentStep: 1
Read-only (no onStepClick — no step is focusable)
- Profile
- Preferences
- Verify
- Done
Purpose
Stepper shows progress through a fixed, known-length, ordered sequence of named steps (checkout, onboarding, setup wizard) — a compound Stepper + Step API built against the verified Navigation/Step Item component set.
Anatomy
Stepper = ordered list (ol) of Step items, each a 24×24px circle indicator (Check icon when Completed, step number when Current/Upcoming) + label, joined by 32×1.5px connector rectangles between consecutive steps.
Variants and states
completed · current · upcoming
When to use
Linear, ordered processes where showing overall progress and remaining steps helps the user, and the full set of steps is known up front.
When not to use
Non-linear or optional-order tasks — a Stepper implies sequence, which would be misleading otherwise. Peer independent actions (use Button Group). Form-field exclusive choice (use Radio Group). Switching content panels (use Tabs). Open-ended chronological event history (use Timeline).
Accessibility
Renders as a real ordered list. aria-current="step" on the Current step. Current/Completed/Upcoming are distinguished by more than color alone (checkmark vs. number, bold vs. regular label weight, fill vs. outline). Requires an accessible name via aria-label or aria-labelledby.
Keyboard behavior
Read-only by default (no onStepClick): steps are not focusable controls. With onStepClick: Completed and Current steps become real buttons in natural Tab order, activated by Enter/Space; Upcoming steps are never focusable or clickable, regardless of onStepClick.
Common mistakes
Letting users click ahead to Upcoming steps — Stepper never allows this even when onStepClick is provided. Treating Stepper as a form/wizard state owner — it only derives visual state from currentStep and relays clicks; the app owns navigation, routing, and step progression.
Properties
React: currentStep (number, zero-based), onStepClick?(index), aria-label / aria-labelledby. Step: children (label) only — no state, description, or orientation prop.
What is the difference between Stepper and Progress Bar?
Progress Bar shows a raw percentage with no per-step identity. Stepper shows named, discrete steps with Completed/Current/Upcoming state.
What is the difference between Stepper and Breadcrumb?
Breadcrumb shows location in a hierarchy. Stepper shows progress through a linear process — see also Breadcrumb's own documented distinction.
What is the difference between Stepper and Timeline?
Timeline is an open-ended chronological log of events. Stepper is a fixed, known-length process with a clear "you are here" progress state — semantically different despite visual similarity.
Does Stepper own routing or wizard state?
No. Stepper only derives Completed/Current/Upcoming from currentStep and relays onStepClick(index). The app owns navigation, routing, and advancing currentStep.
Can Upcoming steps be clicked?
Never. Even when onStepClick is provided, only Completed and Current steps become interactive — Upcoming steps cannot be used to skip ahead.
Tokens used
semantic/action/primarysemantic/surface/defaultsemantic/border/defaultsemantic/text/primarycomponent/surface/content-mutedradius/full
Known limitation
React-first v1 scope, matching the verified Figma contract exactly: fixed horizontal orientation only (no vertical axis exists in Figma), no description/subtitle/metadata/badge/icon slot, no error/disabled/optional/skipped state, no equal-width mode, and no defined behavior for narrow viewports or long (5+) sequences — Figma's own composed example is a fixed-width demo and does not address this. /r distribution deferred to CE-3.
Component API
| Prop | Type | Default | Description |
|---|---|---|---|
| currentStep | number | — | Zero-based index of the current step. Stepper reads this to derive each Step's status — it never mutates it. |
| onStepClick | (index: number) => void | — | Fires when a Completed or Current step is activated. Omit for a fully read-only Stepper. Upcoming steps never fire this. |
| aria-label | string | — | Accessible name for the stepper when no visible label exists. |
| aria-labelledby | string | — | Accessible name reference when a visible label element exists. |
| children | ReactNode | — | Step children, in order. |
| Step.children | ReactNode | — | The step's label — the only content Step accepts (no description/state/orientation prop). |
React example
Copy React example
import { useState } from "react";
import { Stepper, Step } from "@/components/ui/Stepper";
export function Example() {
const [currentStep, setCurrentStep] = useState(1);
return (
<Stepper
currentStep={currentStep}
onStepClick={setCurrentStep}
aria-label="Checkout progress"
>
<Step>Account</Step>
<Step>Shipping</Step>
<Step>Payment</Step>
<Step>Complete</Step>
</Stepper>
);
}