Skip to main content

Stepper

Implemented

Horizontal step indicator — 3-6 steps with state (complete/current/upcoming). Used in checkout, multi-step forms, onboarding.

Categorymolecules
Sourcepatterns/molecules/stepper.html
AI HintUse for grouped UI patterns. Combine atoms into functional units.

Horizontal or vertical step indicator with three visual states — completed (btn-primary fill + check tick), current (btn-primary fill + teal ring), upcoming (muted + border). 3-6 steps recommended; mode="progress" (default) or "explainer". DM-18 2026-08-22.

Stepper

Storefront molecule (Family 7 — Tier 1)

A horizontal or vertical list of numbered steps. In the default mode="progress" it is a real progress tracker: completed steps carry a filled circle + check tick, the current step is filled + ringed (aria-current="step"), and upcoming steps stay muted (DM-18 2026-08-22 — circles are btn-primary at 5.70:1 AA, not raw teal). mode="explainer" renders neutral numbered steps with no progress state, for marketing 1-2-3 walkthroughs. The D-HOME-P5f/P5g/P5h layout mechanics (flex-1 columns, flex-shrink-0 circles, wrapping text) are still current.

Horizontal (default) — completed tick / current ring / upcoming muted

  1. BrowseFind events near you
  2. BuySecure checkout in seconds
  3. EnjoyShow your ticket at the door

Explainer mode — neutral numbered, no progress state

  1. BrowseFind events near you
  2. BuySecure checkout in seconds
  3. EnjoyShow your ticket at the door

Vertical (mobile)

  1. BrowseFind events near you
  2. BuySecure checkout in seconds
  3. EnjoyShow your ticket at the door

Large size

  1. BrowseFind events near you
  2. BuySecure checkout in seconds
  3. EnjoyShow your ticket at the door

Usage

import { Stepper } from '@epasslive/design-system'

const steps = [
  { id: 's1', label: 'Browse', description: 'Find events near you' },
  { id: 's2', label: 'Buy', description: 'Secure checkout in seconds' },
  { id: 's3', label: 'Enjoy', description: 'Show your ticket at the door' },
]

// Progress tracker (default mode)
<Stepper ariaLabel="How it works" steps={steps} currentIndex={1} />

// Marketing walkthrough — neutral numbered steps, no progress state
<Stepper ariaLabel="How it works" steps={steps} mode="explainer" />

// Clickable steps (function prop — render from a client component)
<Stepper
  ariaLabel="Checkout progress"
  steps={steps}
  currentIndex={1}
  onStepClick={(index) => goToStep(index)}
/>

Visual structure (DM-18 three-state, 2026-08-22)

┌───────────────────────┐
│ [✓] Browse            │  circle: btn-primary fill + check tick (completed)
│   Find events...      │  text: min-w-0 flex-1, wraps naturally
└───────────┬───────────┘
            ━━━  connector: flex-1 h-0.5, teal (this leg is done) — aria-hidden
┌───────────────────────┐
│ [2] Buy     (current) │  circle: btn-primary fill + ring-4 ring-ep-teal/20
│   Secure checkout..   │  <li> carries aria-current="step"
└───────────┬───────────┘
            ━━━  connector: border color (this leg is not done yet)
┌───────────────────────┐
│ [3] Enjoy             │  circle: bg-secondary + border, muted number
│   Show your ticket..  │
└───────────────────────┘

Contrast (DM-18): completed/current circles are --ep-btn-primary with an
inverse number/tick — 5.70:1 AA (raw teal was 3.08:1 FAIL). Connectors
stay raw teal: decorative, aria-hidden.
Layout (P5f/P5g/P5h, still current): all columns flex-1 min-w-0 (equal
width); circles flex-shrink-0 (always round); text wraps; connector sits
on the circle's centre line.

Modes — progress vs explainer

mode="progress" (default) is for flows where "where am I?" matters — checkouts, wizards, multi-step forms: completed steps tick, the current step rings and carries aria-current="step", future steps are muted (and disabled when clickable). mode="explainer" is for marketing 1-2-3 walkthroughs ("Browse → Buy → Enjoy") that must not read as "you are mid-checkout": neutral numbered steps, no tick, no ring, nothing disabled. See the live "Explainer mode" preview above.

Step count — 3-6 recommended

3-6 steps is the recommendation (NASA Rule 2); outside that range the component logs a dev-only warning — nothing throws in production. Reality today: the production consumer EventEditorNav passes 7 steps for legacy parity (the legacy event editor had 7 sections), so the dev warning fires in dev builds of the dashboard. A bound decision (relax to ≤8 vs restructure the editor nav) is queued for cascade step 6.

Accessibility

The root element is an <ol role="list"> with an aria-label. Each step is an <li role="listitem">; the current step has aria-current="step" (progress mode only). Steps render as <button type="button"> when clickable, with a focus-visible teal ring. Disabled steps have aria-disabled="true" and tabIndex={-1}.

Touch targets — current state (honest): the hit target is the full circle+label row, but --ep-tap-min (44×44, WCAG 2.5.5) is not yet enforced on the step button — at size="sm" (24px circle) targets can fall below 44×44. A min-h/min-w-[var(--ep-tap-min)] fix is queued (cascade step 6).

Known gap (fix queued): step completion is conveyed visually only — the tick and the filled circle both sit inside an aria-hidden="true" span, so screen readers hear the label but not "completed". An sr-only completed announcement is queued (cascade step 6).

Changelog

  • DM-18 (2026-08-22): completed/current circles moved from raw teal (3.08:1 — WCAG AA FAIL with inverse glyph) to btn-primary (5.70:1 AA PASS, theme-stable light + dark). Upcoming stays muted + border; connectors stay raw teal (aria-hidden decorative track).
  • Explainer mode (2026-08-20): mode prop added — progress semantics became opt-out for marketing walkthroughs.
  • D-HOME-P5e (2026-08-11, reverted 2026-08-12): briefly unified all circles to the same teal + shadow-lg. Reverted the next day — a run of identical circles showed no progress at all and regressed the event editor's step bar. Three states restored in D-UI-08.
  • P5f (2026-08-11, still current): equal column widths via flex-1 on all <li>
  • P5g (2026-08-11, still current): flex-shrink-0 on circles prevents horizontal compression
  • P5h (2026-08-11, still current): layout mechanics — text wraps naturally, connector sits on the circle's centre line, items-start alignment

Used by

  • HowItWorks — packages/storefront/src/components/storefront/HowItWorks.tsx (storefront home marketing section; switches to variant="vertical" below md via matchMedia). Currently runs default progress mode with a static currentIndex={1}; adopting mode="explainer" is queued (cascade step 6).
  • EventEditorNav — packages/storefront/src/components/dashboard/EventEditorNav.tsx (dashboard event editor; clickable steps via onStepClick + allowJumpAhead for edit-existing-events, size="sm", 7 steps — see Step count).

Props

Prop
Type
Default
Description
steps*
StepperStep[]
—

Array of steps. 3-6 recommended — a dev-only warning fires outside that range (NASA Rule 2; production EventEditorNav passes 7 for legacy parity, bound decision queued). Each step has { id: string, label: string, description?: string }.

currentIndex*
number
—

0-based index of the current step. Used for aria-current="step" attribute and disabling future steps when onStepClick is set without allowJumpAhead. Ignored for state in mode="explainer".

onStepClick
(index: number) => void
—

Optional click handler. When provided, steps render as buttons. Steps ahead of currentIndex are disabled unless allowJumpAhead is true — right for editors revisiting an existing record, wrong for strict wizards.

ariaLabel*
string
—

Accessible label for the <ol> container. Describes what the steps are tracking (e.g. "How it works", "Checkout progress"). Required — the molecule ships no default strings (i18n by design).

variant
"horizontal" | "vertical"
horizontal

Layout direction. Horizontal is default (desktop), vertical stacks steps top-to-bottom (mobile).

size
"sm" | "md" | "lg"
md

Circle dimension: sm=h-6/w-6, md=h-8/w-8, lg=h-10/w-10. Note: --ep-tap-min is not yet enforced on clickable steps (fix queued) — sm targets can fall below 44x44.

mode
"progress" | "explainer"
progress

"progress" (default) wears completion/current semantics (tick, ring, aria-current) for checkout-style flows. "explainer" renders neutral numbered steps with no progress state — for marketing 1-2-3 walkthroughs that must not read as "you are mid-checkout".

allowJumpAhead
boolean
false

If true, users can click steps ahead of currentIndex. Default false is right for wizards where later steps depend on earlier answers.

className
string
—

Additional CSS classes for the <ol> container.

id
string
—

Custom id for the <ol>. Defaults to stepper-{React.useId()}.

Props

Preview

Stepper

Prop
Type
Required
Default
Description
steps
Step[]
Required
—

3-6 steps (NASA Rule 2)

currentIndex
number
Required
—

0-based

onStepClick
(index: number) => void
Optional
—

Synced from source — no interface JSDoc yet; document on `Stepper.tsx`.

ariaLabel
string
Required
—

Synced from source — no interface JSDoc yet; document on `Stepper.tsx`.

variant
"horizontal" | "vertical"
Optional
'horizontal'

Visual variant.

size
"sm" | "md" | "lg"
Optional
'md'

Visual size preset.

mode
"progress" | "explainer"
Optional
'progress'

Synced from source — no interface JSDoc yet; document on `Stepper.tsx`.

allowJumpAhead
boolean
Optional
false

Synced from source — no interface JSDoc yet; document on `Stepper.tsx`.

Quick use:<Stepper />

Accessibility

ARIA rolelist

Type

1 rule

NASA Rule 2

steps bounded 3-6 (NASA Rule 2)

ARIA

1 rule

WCAG 4.1.2

role='list' with aria-current='step' on current; non-current steps get aria-disabled if not clickable

Usage

Import

tsxSnippet
import { Stepper } from "@epasslive/design-system"

Example 1

tsxSnippet
<Stepper />

Example 2

tsxSnippet
<Stepper variant="default" />

Anti-patterns

— Do not hardcode data in molecules

— Avoid complex business logic in molecules

Design System v4.1.0 | ePassLive