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:
| Prop | Type | Default | Description |
|---|---|---|---|
stepsrequired | Array<{ id: string; label: string; description?: string }> | — | id is the React key |
currentSteprequired | number | — | Zero-based index of the current step; earlier steps are complete, later ones upcoming |
onStepClick | (stepIndex: number) => void | — | Makes completed steps buttons. Without it every step is static text |
allowSkip | boolean | false | With onStepClick, the current and upcoming steps are clickable too |
orientation | 'horizontal' | 'vertical' | 'horizontal' | Exposed as data-orientation |
aria-label | string | 'Progress' | Names the nav landmark |
className | string | — | On the nav root |
Slots
| Slot | Element | Notes |
|---|---|---|
[data-slot="step-indicator"] | nav | Root. Also data-orientation |
[data-slot="step-indicator-list"] | ol | — |
[data-slot="step-indicator-step"] | li | data-state="complete" | "current" | "upcoming"; aria-current="step" on the current one |
[data-slot="step-indicator-connector"] | div | Line to the next step; absent on the last. --primary after a complete step, --border otherwise. aria-hidden |
[data-slot="step-indicator-body"] | button / div | Circle + text. Also data-state and data-interactive when clickable |
[data-slot="step-indicator-circle"] | span | The numbered / checked circle, a --control-height-md square |
[data-slot="step-indicator-check"] | svg | The check glyph on a complete step |
[data-slot="step-indicator-text"] | span | Wraps label and description |
[data-slot="step-indicator-label"] | span | — |
[data-slot="step-indicator-description"] | span | Present when the step has a description |
/* Consumer CSS: square step markers */
[data-slot="step-indicator-circle"] {
border-radius: var(--radius-sm);
}
Tokens
| Token | Used for |
|---|---|
--control-height-md | Circle size and the connector geometry derived from it, so the two stay aligned under any density |
--primary, --primary-foreground | Complete circle and connector; current circle border, text and ring |
--muted, --muted-foreground, --border | Upcoming circle and connector |
--foreground | Labels |
--ring | Keyboard focus ring on a clickable step |
--duration-fast, --duration-normal | Connector colour and circle transitions |
Accessibility
- The root is a
<nav>landmark named byaria-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 currentlihasaria-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--ringfocus-visible ring. - Connectors and the check glyph are
aria-hidden; the state is carried byaria-currentand the step number. - Transitions are disabled under
prefers-reduced-motion.