Stepper
Wizard progress for multi-step documents. State has two channels, always: the
marker glyph (aria-hidden) AND visually-hidden state text / aria-current.
The stepper is its own named container — labels collapse to screen-reader-only when
it narrows; markers stay.
Not for switching between independent sections — a
stepper asserts order: earlier steps are complete, later ones are not
yet reachable, and the numbers promise a sequence. Sections a reader
may visit in any order are
tabs; if the "steps" can be
done in any order, the numbers are a lie and readers will still
assume they cannot skip ahead.
- Completed: Header
- Completed: Line items
- 3Approvers
- 4Not started: Review
Narrow container (labels collapse, names retained)
- Completed: Header
- 2Line items
- 3Not started: Review
Markup
<ol class="bo-stepper" role="list">
<li class="bo-stepper__step" data-state="done">
<span class="bo-stepper__marker" aria-hidden="true">✓</span>
<span class="bo-stepper__label"><span class="bo-visually-hidden">Completed: </span>Header</span>
</li>
<li class="bo-stepper__step" aria-current="step">
<span class="bo-stepper__marker">2</span>
<span class="bo-stepper__label">Line items</span>
</li>
<li class="bo-stepper__step">
<span class="bo-stepper__marker">3</span>
<span class="bo-stepper__label"><span class="bo-visually-hidden">Not started: </span>Review</span>
</li>
</ol> Quick reference generated
| Class | Role |
|---|---|
| .bo-stepper | block (root) |
| .bo-stepper__label | part: label |
| .bo-stepper__marker | part: marker |
| .bo-stepper__step | part: step |
State attributes: data-statearia-current
API generated from CSS
| Blocks | bo-stepper |
|---|---|
| Parts | bo-stepper__labelbo-stepper__markerbo-stepper__step |
| data-* hooks | data-state="done" |
| ARIA styled | aria-current |
| Component tokens | --bo-stepper-marker-bg--bo-stepper-marker-fg |
| Named containers | bo-stepper |
| JS required | None to display progress. initWizard() is opt-in and only for the case where the stepper drives a multi-step flow: it shows one [data-wizard-panel] at a time and keeps this stepper in sync, setting data-state=done behind the current step and aria-current=step on it. |
aria-current="step"marks the current step; do not combine withdata-state="done"(current wins by source order but the combination is out of contract).- Steps are inert
<li>by contract — interactive (clickable) steps are not yet specified. - Print: markers render outlined black-on-white automatically.
Contrast — 24 AA pairs this component uses (generated, CI-gated)
| Text | on | Light | Dark |
|---|---|---|---|
| text-primary | bg-surface | 17.74 | 16.15 |
| text-primary | bg-canvas | 16.98 | 18.08 |
| text-secondary | bg-surface | 10.31 | 11.46 |
| text-secondary | bg-muted | 9.37 | 9.75 |
| text-muted | bg-surface | 7.56 | 6.65 |
| text-muted | bg-muted | 6.87 | 5.66 |
| text-inverse | accent-solid | 5.47 | 5.47 |
| text-inverse | danger-solid | 4.83 | 4.83 |
| bg-surface | text-primary | 17.74 | 16.15 |
| success-text | success-subtle | 4.79 | 10.17 |
| success-text | bg-surface | 5.02 | 12.02 |
| success-text | bg-canvas | 4.8 | 13.46 |
| text-primary | bg-hover | 16.12 | 13.75 |
| text-primary | bg-selected | 17.01 | 11.86 |
| text-secondary | bg-selected | 9.88 | 8.41 |
| text-primary | bg-surface-raised | 17.74 | 14.51 |
| text-secondary | bg-canvas | 9.86 | 12.82 |
| border-control | bg-muted | 4.39 | 5.66 |
| accent | bg-muted | 4.97 | 7.72 |
| warning-strong | bg-muted | 4.56 | 6.69 |
| danger | bg-muted | 4.39 | 5.19 |
| text-primary | bg-muted | 16.12 | 13.75 |
| success-text | bg-muted | 4.56 | 10.23 |
| danger-text | bg-muted | 5.88 | 7.57 |
Maturity generated
- Introduced
- v0.1.0
- CSS floor
- Chrome/Edge 105 · Firefox 110 · Safari 16
- Alignment scored
- 2026-08-23
- Assistive-tech evidence
- None recorded
Pin this component's first release with npm i @busy-office/ui@0.1.0.
The floor is derived from this component's stylesheet alone — set by
@container — so it sits at or below the
whole framework's Chrome/Edge 119 · Firefox 129 · Safari 17.5.
No assistive-technology runtime result is recorded for any component: it needs a human listening to VoiceOver and NVDA on owner hardware; no gate in this repo can simulate announcement behaviour.
What automation covers instead is test:axe across every page at 1440 and 390, and check:forced-colors — see
accessibility.
Design-system alignment generated
100% (15/15) — how closely this component follows the design language. Each rule is scored 0-3 against a checkable citation; rules that don't apply leave the denominator. Scored 2026-08-23.
| Rule | Score | Evidence |
|---|---|---|
| Typography | 3 / 3 | marker on --bo-font-size-sm with tabular-nums, label on --bo-density-font-size; no raw font-size |
| Colour | 3 / 3 | raw hex appears ONLY inside @media print, where theme tokens would be wrong on paper; state is two-channel via a real ✓ in the marker markup |
| Spacing | 3 / 3 | the 1.75rem marker and 2px connector both carry their reason (94.6b) |
| Interaction | n/a | an <ol> reporting position; the wizard’s controls live elsewhere |
| Content | 3 / 3 | opener carries the wrong-choice clause: "Not for switching between independent sections" — a stepper asserts order and its numbers promise a sequence; free-order sections are tabs. Blind re-scored 2026-08-23 (Polish round 14); consistent with the ApiTable note that steps are inert li elements, so section-switching is doubly wrong. |
| Fit | 3 / 3 | discrete named steps in a multi-step document; container-queried for narrow shells |