Skip to main content

Step Indicator

A step indicator shows where the user is in a sequence: completed steps get a check on a --primary circle, the current one a ringed outline, the rest a muted number. It is a <nav aria-label="Progress"> around an ordered list; the current item carries aria-current="step".

import StepIndicator from '@zuilib/components/step-indicator'

Horizontal

Each step takes an equal share of the width. description is optional secondary text under the label.

Loading example

Vertical

orientation="vertical" stacks the steps with the connector running down the left edge; labels sit beside the circles.

Loading example

Clickable steps

Without onStepClick every step is static text and nothing is in the tab order. With it, completed steps become <button>s so the user can go back; allowSkip also makes the current and upcoming steps clickable.

Loading example
Loading example

Props

StepIndicatorProps extends the <nav> attributes with:

PropTypeDefaultDescription
stepsrequiredArray<{ id: string; label: string; description?: string }>id is the React key
currentSteprequirednumberZero-based index of the current step; earlier steps are complete, later ones upcoming
onStepClick(stepIndex: number) => voidMakes completed steps buttons. Without it every step is static text
allowSkipbooleanfalseWith onStepClick, the current and upcoming steps are clickable too
orientation'horizontal' | 'vertical''horizontal'Exposed as data-orientation
aria-labelstring'Progress'Names the nav landmark
classNamestringOn the nav root

Slots

SlotElementNotes
[data-slot="step-indicator"]navRoot. Also data-orientation
[data-slot="step-indicator-list"]ol
[data-slot="step-indicator-step"]lidata-state="complete" | "current" | "upcoming"; aria-current="step" on the current one
[data-slot="step-indicator-connector"]divLine to the next step; absent on the last. --primary after a complete step, --border otherwise. aria-hidden
[data-slot="step-indicator-body"]button / divCircle + text. Also data-state and data-interactive when clickable
[data-slot="step-indicator-circle"]spanThe numbered / checked circle, a --control-height-md square
[data-slot="step-indicator-check"]svgThe check glyph on a complete step
[data-slot="step-indicator-text"]spanWraps label and description
[data-slot="step-indicator-label"]span
[data-slot="step-indicator-description"]spanPresent when the step has a description
/* Consumer CSS: square step markers */
[data-slot="step-indicator-circle"] {
border-radius: var(--radius-sm);
}

Tokens

TokenUsed for
--control-height-mdCircle size and the connector geometry derived from it, so the two stay aligned under any density
--primary, --primary-foregroundComplete circle and connector; current circle border, text and ring
--muted, --muted-foreground, --borderUpcoming circle and connector
--foregroundLabels
--ringKeyboard focus ring on a clickable step
--duration-fast, --duration-normalConnector colour and circle transitions

Accessibility

  • The root is a <nav> landmark named by aria-label ("Progress" by default); pass your own when a page has more than one.
  • Steps are an <ol>, so screen readers announce "1 of 4" and the order. The current li has aria-current="step".
  • Static steps render as divs: nothing to tab onto. Clickable steps are real <button type="button">s, reachable with Tab and activated with Enter or Space, with the shared --ring focus-visible ring.
  • Connectors and the check glyph are aria-hidden; the state is carried by aria-current and the step number.
  • Transitions are disabled under prefers-reduced-motion.
  • Tabs: parallel sections rather than an ordered sequence.
  • Progress: a single bar when the steps have no names.
  • Button: Back / Next controls beside the indicator.