Components · Wayfinding
Stepper
Ordered multi-step progress indicator for sequential flows. Split from zetta-navigation-v1_4.md (removed in v1.5) in v1.4.
Preview
Build with the skill
No package to install — hand this to your AI to generate or migrate the component in your stack.
Apply the Zetta "Stepper" component to this project using the zetta-design-md skill.
WHEN TO USE
- Migrating or fixing existing code (the common case): audit the current implementation against the `stepper` spec and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `stepper` spec in this project's stack.
AUDIT & FIX (when the component already exists)
Compare it to the skill's spec and fix, don't just append: hardcoded colors/hex, off-scale radius, wrong fonts, dark/navy fills, shadows used in place of borders (or missing borders), brand lime used as a CTA or interaction (must be primary indigo), and any missing or wrong hover / focus / active / disabled / invalid state or missing 2px focus ring. Replace each with the correct Zetta token.
ALWAYS
- Pull the authoritative spec and tokens from the skill — never guess or hardcode values.
- Use this project's own component conventions and framework idioms.
- Honor the Zetta guardrails (brand = identity only, primary = interaction, shadows for overlays) and verify in Light, Dark, and Accessibility. Anatomy & rules
① ────────── ② ────────── ③ ────────── ④
Account Payment Review Done
↑ active ↑ upcoming ↑ upcoming ↑ upcoming
(primary) (surface- (surface- (surface-
secondary) secondary) secondary)
Completed step:
✓ ══════════ ②
Details Payment
(primary-bg (upcoming)
+ check icon)
| State | Indicator fill | Indicator border | Icon | Label |
|---|---|---|---|---|
| Upcoming | {colors.surface-secondary} |
{colors.hairline} |
Step number | {colors.muted} / 400 |
| Active | {colors.primary} |
{colors.primary} |
Step number | {colors.ink} / 500 |
| Completed | {colors.primary-bg} |
{colors.primary} |
check_circle filled |
{colors.muted} / 400 |
| Error | {colors.danger-bg} |
{colors.danger} |
error icon |
{colors.danger} / 400 |
| Disabled | {colors.disabled-bg} |
{colors.disabled-border} |
Step number | {colors.disabled-text} |
Connector: {colors.hairline} when step to the left is upcoming or active. {colors.primary} when step to the left is completed.
When to use
- Multi-step forms or wizards where steps must be completed in order.
- When showing progress through a linear flow is meaningful to the user.
When not to use
- If steps can be completed in any order — use standard Tabs.
- More than 7 steps — break into sections.
- Single-step flows — just show the form.
Do
- Mark all completed steps with a check icon — not just the most recently completed.
- Allow users to navigate back to completed steps if the flow permits (set
navigable={true}). - Validate the current step before advancing.
Don't
- Don't allow users to skip ahead to incomplete steps.
- Don't use more than 7 steps in one stepper.
- Don't truncate step labels.
- Step labels: 1–3 words. Match the action: "Account details", "Payment", "Confirm order".
- Use noun phrases or short verb phrases — not full sentences.
- Completed step
aria-label: "Step 1 of 4: Account details — completed." - Active step
aria-label: "Step 2 of 4: Payment — current step." - Error step
aria-label: "Step 3 of 4: Review — error, please correct before continuing."
Keyboard
| Key | Behavior |
|---|---|
Tab |
Move focus through step indicators |
Enter / Space |
Navigate to step (if navigable and step is completed) |
Accessibility
role="list"onstepper-tabs-list,role="listitem"on each step trigger.- Each step indicator:
aria-label="Step N of M: [label] — [state]". - Active step:
aria-current="step". - Connector lines:
aria-hidden="true"— decorative. - Focus ring:
2px solid {colors.border-focus}at 2px offset on each trigger.
Spec
Values and token references resolved straight from the Zetta spec.
stepper-tabs-list
- display
- flex
- flexDirection
- row
- alignItems
- center
- gap
- 0
stepper-tabs-connector
- flex
- 1
- height
- 1px
- backgroundColor
- {colors.hairline}
stepper-tabs-connector-completed
- backgroundColor
- {colors.primary}
stepper-tabs-trigger
- display
- flex
- flexDirection
- column
- alignItems
- center
- gap
- {spacing.xs}
- cursor
- pointer
- position
- relative
stepper-tabs-trigger-focus
- outline
- 2px solid {colors.border-focus}
- outlineOffset
- 2px
- borderRadius
- {rounded.base}
stepper-step-indicator
- width
- 28px
- height
- 28px
- borderRadius
- {rounded.full}
- backgroundColor
- {colors.surface-secondary}
- borderColor
- {colors.hairline}
- borderWidth
- 1px
- textColor
- {colors.muted}
- fontFamily
- Geist
- fontSize
- 13px
- fontWeight
- 500
- display
- flex
- alignItems
- center
- justifyContent
- center
stepper-step-indicator-active
- backgroundColor
- {colors.primary}
- borderColor
- {colors.primary}
- textColor
- {colors.primary-text}
stepper-step-indicator-completed
- backgroundColor
- {colors.primary-bg}
- borderColor
- {colors.primary}
- textColor
- {colors.primary}
stepper-step-indicator-error
- backgroundColor
- {colors.danger-bg}
- borderColor
- {colors.danger}
- textColor
- {colors.danger}
stepper-step-label
- textColor
- {colors.muted}
- fontFamily
- Geist
- fontSize
- 12px
- fontWeight
- 400
stepper-step-label-active
- textColor
- {colors.ink}
- fontWeight
- 500
stepper-step-label-completed
- textColor
- {colors.muted}