skrewww

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)

  1. Payment
  2. Complete

currentStep: 1

Read-only (no onStepClick — no step is focusable)

  1. Profile
  2. Preferences
  3. Verify
  4. 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

PropTypeDefaultDescription
currentStepnumberZero-based index of the current step. Stepper reads this to derive each Step's status — it never mutates it.
onStepClick(index: number) => voidFires when a Completed or Current step is activated. Omit for a fully read-only Stepper. Upcoming steps never fire this.
aria-labelstringAccessible name for the stepper when no visible label exists.
aria-labelledbystringAccessible name reference when a visible label element exists.
childrenReactNodeStep children, in order.
Step.childrenReactNodeThe 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>
  );
}