Components · Wayfinding
Progress tracker
A read-only map of a multi-step flow — checkout, onboarding, a wizard. Completed steps fill Primary with a check, the current step rings Primary, upcoming steps stay neutral, and an errored step goes danger. The connectors fill in behind you.
Preview
Cart
Shipping
3
Payment
4
Review
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 "Progress tracker" 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 `progress-tracker` spec (variants: completed, current, upcoming, error) and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `progress-tracker` 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
See Navigation — Stepper for full state table. Progress Tracker uses the same stepper token set.
| State | Indicator | Label |
|---|---|---|
| Upcoming | {colors.surface-muted} |
{colors.muted} |
| Active | {colors.primary} |
{colors.ink} bold |
| Completed | {colors.primary} + check |
{colors.muted} |
| Error | {colors.danger} + error icon |
{colors.danger} |
When to use
- Multi-step forms where the user needs to know how many steps exist and how far along they are.
- Onboarding flows (3–7 steps).
- Checkout or configuration wizards.
When not to use
- Single-step forms — no tracker needed.
- Flows with more than 7 steps — consider a sidebar stepper or breaking the flow into sections.
- Read-only audit trail of actions taken — use a Timeline component pattern instead.
- Tab navigation between sections — use Tabs.
Do
- Show step labels in the full variant — users need to understand what each step is, not just its number.
- Mark a step as
errorwhen the user returns to it and it has invalid data. - Always show the current step as the active node — never skip it.
- Enable clicking on completed steps to go back — the node is the navigation control.
Don't
- Don't show more than 7 steps in the full variant — truncate or restructure the flow.
- Don't update the tracker while an async step operation is pending — wait for success before advancing.
- Don't hide the tracker on mobile — reduce to the compact variant instead.
- Step labels: noun phrase or gerund. "Account info", "Payment", "Review" — not "Step 1", "Step 2".
- Max label length: 12 characters before truncation in the
maxWidth: 80pxconstraint. Keep short. - Error label: same label text — the error state is communicated by the node icon, not the label.
Keyboard
Progress Tracker is non-interactive by default. When navigable={true}, completed steps are clickable — see Stepper for keyboard spec.
| Key | Behavior |
|---|---|
Tab |
Moves focus to each completed-step node (clickable to navigate back) |
Enter / Space |
Navigates back to the focused completed step |
Accessibility
- ARIA role:
role="list"on the tracker container. Each step isrole="listitem". - Current step:
aria-current="step"on the current step node. - Completed steps: if clickable (back navigation), they are
role="button"witharia-labeldescribing the step name and state: "Step 2: Payment — completed, click to go back". - Upcoming steps: not interactive —
aria-disabled="true"if rendered as buttons, or use a<span>instead. - Progress announcement: wrap the tracker in
aria-label="Form progress: Step 2 of 4"on the container<nav>or<div role="navigation">. - Error step:
aria-invalid="true"on the step and announce the error via a live region.
Spec
Values and token references resolved straight from the Zetta spec.
progress-tracker
- display
- flex
- alignItems
- flex-start
- width
- 100%
- gap
- 0
progress-tracker-step
- display
- flex
- flexDirection
- column
- alignItems
- center
- flex
- 1
- position
- relative
progress-tracker-node
- width
- 28px
- height
- 28px
- borderRadius
- {rounded.full}
- borderWidth
- 2px
- borderStyle
- solid
- display
- flex
- alignItems
- center
- justifyContent
- center
- fontFamily
- Geist
- fontSize
- 13px
- fontWeight
- 500
- lineHeight
- 1
- zIndex
- 1
- position
- relative
- flexShrink
- 0
progress-tracker-node-completed
- backgroundColor
- {colors.primary}
- borderColor
- {colors.primary}
- textColor
- {colors.primary-text}
- iconSize
- 14px
- iconColor
- {colors.primary-text}
progress-tracker-node-current
- backgroundColor
- {colors.canvas}
- borderColor
- {colors.primary}
- textColor
- {colors.primary}
progress-tracker-node-upcoming
- backgroundColor
- {colors.canvas}
- borderColor
- {colors.hairline}
- textColor
- {colors.muted}
progress-tracker-node-error
- backgroundColor
- {colors.danger-bg}
- borderColor
- {colors.danger}
- textColor
- {colors.danger}
- iconSize
- 14px
- iconColor
- {colors.danger}
progress-tracker-connector
- height
- 2px
- flex
- 1
- position
- absolute
- top
- 13px
- left
- calc(50% + 14px)
- right
- calc(-50% + 14px)
- zIndex
- 0
progress-tracker-connector-completed
- backgroundColor
- {colors.primary}
progress-tracker-connector-upcoming
- backgroundColor
- {colors.hairline}
progress-tracker-label
- fontFamily
- Geist
- fontSize
- 12px
- fontWeight
- 400
- textColor
- {colors.muted}
- marginTop
- {spacing.xs}
- textAlign
- center
- maxWidth
- 80px
- overflow
- hidden
- textOverflow
- ellipsis
- whiteSpace
- nowrap
progress-tracker-label-current
- fontWeight
- 500
- textColor
- {colors.ink}
progress-tracker-label-completed
- textColor
- {colors.primary}
progress-tracker-label-error
- textColor
- {colors.danger}
progress-tracker-compact
- display
- flex
- alignItems
- center
- gap
- {spacing.xs}
progress-tracker-dot
- width
- 8px
- height
- 8px
- borderRadius
- {rounded.full}
progress-tracker-dot-completed
- backgroundColor
- {colors.primary}
progress-tracker-dot-current
- backgroundColor
- {colors.primary}
- width
- 24px
- borderRadius
- {rounded.full}
progress-tracker-dot-upcoming
- backgroundColor
- {colors.hairline}
progress-tracker-vertical
- flexDirection
- column
- alignItems
- flex-start
- gap
- 0
progress-tracker-vertical-step
- flexDirection
- row
- alignItems
- flex-start
- gap
- {spacing.sm}
- flex
- none
progress-tracker-vertical-connector
- width
- 2px
- flex
- 1
- minHeight
- 24px
- marginLeft
- 13px
- backgroundColor
- {colors.hairline}
progress-tracker-vertical-connector-completed
- backgroundColor
- {colors.primary}
progress-tracker-mobile
- display
- flex
- alignItems
- center
- gap
- {spacing.sm}
progress-tracker-mobile-label
- fontFamily
- Geist
- fontSize
- 13px
- fontWeight
- 500
- textColor
- {colors.ink}
progress-tracker-mobile-sublabel
- fontFamily
- Geist
- fontSize
- 12px
- fontWeight
- 400
- textColor
- {colors.muted}
Known gaps
- Clickable completed-step back navigation interaction spec — confirmed deferred to runtime implementation; no token gaps remain.