Stepper
import { Stepper } from '@absoluteui/components/stepper';import '@absoluteui/components/stepper/style.css';Using Angular? See Stepper for Angular.
What it does
Section titled “What it does”Show progress through a multi-step flow and let users move between steps. Stepper renders navigation: a labelled <nav> with an ordered list of steps. Reachable steps are links (when they have an href) or buttons; the current step carries aria-current="step"; unreachable steps are text. In linear mode, a step after the current one is reachable only when every step before it is complete. Activation emits a cancelable aui-navigate, so the application can guard the move, and then aui-change, so it can switch its own panels. Step status (complete, error) is data owned by the application; the stepper never infers completion.
When to use
Section titled “When to use”- Multi-step flows with a known, short sequence of steps, such as checkout, onboarding, or a setup wizard.
- Showing which steps are complete, which step is current, and which are still ahead.
- Letting users return to earlier steps (and, in non-linear flows, jump ahead).
When not to use
Section titled “When not to use”- Switching between peer views that have no order: use Tabs.
- Showing where a page sits in a hierarchy: use Breadcrumb.
- Reporting progress of a single task: use Progress Bar.
- Flows with more than about 7 steps or with branching paths: split the flow or use a list of sections.
Examples
Section titled “Examples”Checkout progress with a completed first step
Section titled “Checkout progress with a completed first step”import { Stepper } from '@absoluteui/components/stepper';
const host = document.createElement('div');document.body.append(host);Stepper.create(host, { label: 'Checkout progress', steps: [ { id: 'cart', label: 'Cart', status: 'complete' }, { id: 'shipping', label: 'Shipping' }, { id: 'payment', label: 'Payment' }, { id: 'review', label: 'Review', optional: true }, ], value: 'shipping',});Linear wizard: guard forward moves and unlock steps on completion
Section titled “Linear wizard: guard forward moves and unlock steps on completion”import { Stepper, type StepperChangeDetail, type StepperNavigateDetail, type StepperStep } from '@absoluteui/components/stepper';
const host = document.querySelector<HTMLElement>('#signup-steps');const form = document.querySelector<HTMLFormElement>('#signup-form');const next = document.querySelector<HTMLButtonElement>('#signup-next');if (host && form && next) { let steps: StepperStep[] = [ { id: 'account', label: 'Account' }, { id: 'profile', label: 'Profile', description: 'Name and photo' }, { id: 'finish', label: 'Finish' }, ]; const stepper = Stepper.create(host, { label: 'Sign-up steps', steps }); form.dataset.step = stepper.value; // Guard forward moves: the current section must be valid first. host.addEventListener('aui-navigate', (event) => { const { index, previous } = (event as CustomEvent<StepperNavigateDetail>).detail; const from = steps.findIndex((s) => s.id === previous); if (index > from && !form.reportValidity()) event.preventDefault(); }); host.addEventListener('aui-change', (event) => { form.dataset.step = (event as CustomEvent<StepperChangeDetail>).detail.value; }); // "Next": mark the current step complete (unlocking the next one) and move on. next.addEventListener('click', () => { if (!form.reportValidity()) return; const index = steps.findIndex((s) => s.id === stepper.value); steps = steps.map((s, i): StepperStep => (i === index ? { ...s, status: 'complete' } : s)); const following = steps[index + 1]; stepper.update({ steps, ...(following ? { value: following.id } : {}) }); form.dataset.step = stepper.value; });}Non-linear vertical stepper with link steps and an error step
Section titled “Non-linear vertical stepper with link steps and an error step”import { Stepper } from '@absoluteui/components/stepper';
const host = document.querySelector<HTMLElement>('#application-steps');if (host) { const stepper = Stepper.create(host, { label: 'Application sections', orientation: 'vertical', linear: false, steps: [ { id: 'personal', label: 'Personal details', href: '/apply/personal', status: 'complete' }, { id: 'income', label: 'Income', href: '/apply/income', status: 'error', description: '2 fields need attention' }, { id: 'documents', label: 'Documents', href: '/apply/documents' }, { id: 'submit', label: 'Submit', href: '/apply/submit', disabled: true }, ], value: 'documents', }); host.dataset.canSubmit = String(stepper.isReachable('submit'));}import { Stepper, type StepperChangeDetail, type StepperNavigateDetail } from '@absoluteui/components/stepper';import '@absoluteui/components/stepper/style.css';
const host = document.querySelector<HTMLElement>('#checkout-steps');const form = document.querySelector<HTMLFormElement>('#checkout');if (host && form) { const steps = [ { id: 'shipping', label: 'Shipping' }, { id: 'payment', label: 'Payment' }, { id: 'review', label: 'Review' }, ]; Stepper.create(host, { label: 'Checkout progress', steps, linear: false }); host.addEventListener('aui-navigate', (event) => { const { index, previous } = (event as CustomEvent<StepperNavigateDetail>).detail; const from = steps.findIndex((s) => s.id === previous); if (index > from && !form.reportValidity()) event.preventDefault(); }); host.addEventListener('aui-change', (event) => { form.dataset.step = (event as CustomEvent<StepperChangeDetail>).detail.value; });}More usage
Section titled “More usage”import { Stepper } from '@absoluteui/components/stepper';
const host = document.createElement('div');document.body.append(host);Stepper.create(host, { label: 'Checkout progress', steps: [ { id: 'cart', label: 'Cart', status: 'complete' }, { id: 'shipping', label: 'Shipping' }, { id: 'payment', label: 'Payment' }, ], value: 'shipping',});Anatomy
Section titled “Anatomy”<div class="aui-stepper aui-stepper--horizontal"> (consumer host; inline-size container) <nav class="aui-stepper__nav" aria-label="Checkout progress"> <ol class="aui-stepper__list"> <li class="aui-stepper__step" data-state="complete" data-reachable="true"> <button type="button" class="aui-stepper__control" data-index="0"> (<a href> when the step has an href) <span class="aui-stepper__indicator" aria-hidden="true"><svg class="aui-icon">...</svg></span> <span class="aui-stepper__text"> <span class="aui-stepper__label">Cart</span> <span class="aui-stepper__optional">Optional</span> (optional: true) <span class="aui-stepper__description">...</span> (description) </span> <span class="aui-stepper__status">Completed</span> (visually hidden; "Error" for errors) </button> </li> <li class="aui-stepper__step" data-state="current"> <span class="aui-stepper__control" aria-current="step">...<span class="aui-stepper__indicator" aria-hidden="true">2</span>...</span> </li> <li class="aui-stepper__step" data-state="upcoming"> <span class="aui-stepper__control">...</span> (unreachable: text) </li> </ol> </nav></div>The connector line between steps is a decorative ::after on .aui-stepper__step.
API reference
Section titled “API reference”Factory
Section titled “Factory”Stepper.create(host: HTMLElement, options: StepperOptions): StepperGlobal configuration key: stepper (see Configuration).
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
label |
string |
Accessible name of the <nav> (aria-label), e.g. “Checkout progress”. Must not be empty. Required. | |
steps |
readonly { id: string; label: string; description?: string; href?: string; status?: 'complete' | 'error'; optional?: boolean; disabled?: boolean }[] |
Steps in order (at least one). Ids must be unique and non-empty; labels must be non-empty. status is consumer-owned; href renders a reachable step as a link. Required. | |
value |
string |
"id of the first step" |
Current step id. Must be the id of an existing step. |
orientation |
'horizontal' | 'vertical' |
"horizontal" |
Layout. A horizontal stepper renders vertically when its container is 30rem wide or narrower. Globally configurable. |
linear |
boolean |
true |
When true, a step after the current one is reachable only when every step before it has status ‘complete’. Earlier steps are always reachable unless disabled. Globally configurable. |
direction |
'ltr' | 'rtl' |
"ltr" |
‘rtl’ sets dir=“rtl” on the <nav>; ‘ltr’ leaves the document direction in effect. Globally configurable. Inherited from common configuration. |
icons |
Partial<{ complete: IconRef; error: IconRef }> |
Icon slot overrides for the step indicator. |
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
host |
HTMLElement |
Consumer host; its children are owned by the component. |
nav |
HTMLElement |
The <nav> landmark. |
value |
string |
Current step id. |
options |
Readonly<StepperOptions> |
Current instance options. |
Methods
Section titled “Methods”| Method | Description |
|---|---|
state(id: string): StepperStepState | null |
Resolved state (‘current’ | ‘complete’ | ‘error’ | ‘upcoming’) of a step, or null for an unknown id. |
isReachable(id: string): boolean |
Whether the user can activate the step in the current state. False for the current step, disabled steps, and unknown ids. |
select(id: string): void |
Make a step current programmatically. No events and no reachability check. Throws STEPPER_OPTIONS for an unknown id. |
update(options: Partial<StepperOptions>): void |
Merge, validate, and apply options. When the current id no longer exists and no value is given, the first step becomes current. No events. |
destroy(): void |
Remove rendering, host classes, listeners, and the configuration subscription. |
Events
Section titled “Events”| Event | Detail | Bubbles | Cancelable | Description |
|---|---|---|---|---|
aui-navigate |
{ id: string; index: number; step: StepperStep; previous: string } |
Yes | Yes | The user activated a reachable step (unmodified primary click, Enter, or Space on a button). preventDefault keeps the current step and stops link navigation. |
aui-change |
{ value: string; previous: string } |
Yes | No | The current step changed after an uncanceled aui-navigate. |
CSS classes
Section titled “CSS classes”All rules live in the aui.components cascade layer, so application styles override them without specificity battles.
| Class | Purpose |
|---|---|
aui-stepper |
Host; an inline-size container. |
aui-stepper--horizontal | --vertical |
Orientation modifiers on the host (the configured orientation). |
aui-stepper__nav |
The <nav> landmark. |
aui-stepper__list |
The <ol>. |
aui-stepper__step |
An <li>; data-state=current|complete|error|upcoming, data-reachable=true, data-disabled=true. |
aui-stepper__control |
<a>, <button>, or <span> for the step. |
aui-stepper__indicator |
Decorative number or icon circle (aria-hidden). |
aui-stepper__text | __label | __optional | __description |
Step text parts. |
aui-stepper__status |
Visually hidden status text (“Completed”, “Error”). |
Icon slots
Section titled “Icon slots”| Slot | Default icon |
|---|---|
complete |
check |
error |
alert-circle |
Design tokens
Section titled “Design tokens”Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.
--aui-border-width-strong | --aui-color-border-strong | --aui-color-error-fg | --aui-color-focus-ring | --aui-color-on-primary | --aui-color-primary | --aui-color-surface | --aui-color-text | --aui-color-text-disabled | --aui-color-text-muted | --aui-control-height-sm | --aui-focus-ring-offset | --aui-focus-ring-width | --aui-font-family-sans | --aui-font-size-md | --aui-font-size-sm | --aui-font-weight-semibold | --aui-icon-size-sm | --aui-line-height-tight | --aui-radius-pill | --aui-radius-sm | --aui-space-1 | --aui-space-2 | --aui-space-4
TypeScript definitions
Section titled “TypeScript definitions”type StepperOrientation = 'horizontal' | 'vertical';type StepperStepStatus = 'complete' | 'error';type StepperStepState = 'complete' | 'error' | 'current' | 'upcoming';interface StepperConfig { orientation: StepperOrientation; linear: boolean }interface StepperStep { id: string; label: string; description?: string; href?: string; status?: StepperStepStatus; optional?: boolean; disabled?: boolean }interface StepperIcons { complete: IconRef; error: IconRef }interface StepperOptions extends Partial<StepperConfig> { label: string; steps: readonly StepperStep[]; value?: string; direction?: Direction; icons?: Partial<StepperIcons> }interface StepperNavigateDetail { id: string; index: number; step: StepperStep; previous: string }interface StepperChangeDetail { value: string; previous: string }Accessibility
Section titled “Accessibility”Pattern: Navigation landmark with an ordered list of steps (progress indicator). Reachable steps are native links or buttons; the current step is marked with aria-current=“step”. Not a tablist.
Keyboard
Section titled “Keyboard”| Key | Context | Action |
|---|---|---|
| Tab / Shift+Tab | Any | Moves between reachable steps (native tab order). The current step and unreachable steps are not Tab stops. |
| Enter | Reachable step focused | Emits cancelable aui-navigate; unless prevented, the step becomes current and aui-change is emitted. Links then navigate natively. |
| Space | Reachable button step focused | Same as Enter (native button activation). |
| Element | Role |
|---|---|
| <nav> | navigation |
| <ol> | list |
| <li> | listitem |
| reachable step with href | link (native <a href>) |
| reachable step without href | button (native <button type=“button”>) |
| current or unreachable step | none (text <span>) |
ARIA attributes
Section titled “ARIA attributes”| Attribute | Element | Value |
|---|---|---|
aria-label |
<nav> | label option |
aria-current |
current step control | step |
aria-hidden |
step indicator (number or icon) | true |
dir |
<nav> | rtl when direction is ‘rtl’; absent otherwise |
Each reachable step is one Tab stop. After a button step is activated, focus moves to the new current step (tabindex=-1), so focus is not lost when the activated control is re-rendered as text. After a link step is activated, focus is not moved. update() and configuration changes rebuild the list and do not restore focus.
WCAG 2.2 success criteria addressed
Section titled “WCAG 2.2 success criteria addressed”| Criterion | Level | Note |
|---|---|---|
| 1.3.1 Info and Relationships | A | |
| 1.4.1 Use of Color | A | |
| 2.1.1 Keyboard | A | |
| 2.4.3 Focus Order | A | |
| 2.4.7 Focus Visible | AA | |
| 4.1.2 Name, Role, Value | A |
Automated test results: Accessibility and compliance.
Notes and known caveats
Section titled “Notes and known caveats”- Step status is conveyed as text: a visually hidden “Completed” or “Error” inside the step control and a visible “Optional” hint, not by color or icon alone.
- The ordered list conveys the number of steps and each step’s position.
- Disabled and unreachable steps are plain text, so they are announced but not operable.
- Manual screen-reader verification (NVDA, JAWS, VoiceOver) is pending.
- Unreachable steps are not exposed as disabled controls; users hear them as text without an explanation of why they cannot be activated.
Behavior
Section titled “Behavior”States
Section titled “States”Step states: upcoming, current, complete, error. Modifiers: optional, disabled, reachable, unreachable. Interaction: hover (label underline on reachable steps), focus-visible. Layout: horizontal, vertical.
State transitions
Section titled “State transitions”| From | Trigger | To |
|---|---|---|
| step A current | reachable step B activated | aui-navigate; unless prevented, B current and aui-change |
| step A current | aui-navigate prevented |
A stays current; a link step does not navigate |
| any | select(id) |
id current, no events |
| any | update({ steps }) with new statuses |
reachability recomputed |
| any | update({ steps }) without the current id |
first step current, no events |
| horizontal | container 30rem or narrower | vertical layout (class unchanged) |
Accessibility notes
Section titled “Accessibility notes”- Status is never conveyed by color or icon alone (WCAG 1.4.1): hidden status text plus a visible “Optional” hint.
- After a button step is activated, focus moves to the new current step (
tabindex="-1"), because the activated button is re-rendered as text. - Unreachable steps are text without an explanation; describe the rule near the stepper (for example “Complete this step to continue”) when it is not obvious.
- Manual screen-reader verification is pending (see Known limitations).
Responsive behavior
Section titled “Responsive behavior”The host is a size container. A horizontal stepper uses the vertical layout when its own container is 30rem wide or narrower (@container (max-width: 30rem)), independent of the viewport. Labels and descriptions wrap and break anywhere. Horizontal steps share the width equally.
Right-to-left
Section titled “Right-to-left”Logical properties throughout: horizontal steps and connectors follow the inline direction, and the vertical connector runs along the inline-start edge. direction: 'rtl' sets dir="rtl" on the <nav>; with 'ltr' (default) no dir is set, so an ancestor dir="rtl" also applies. Icons are not mirrored.
Theming
Section titled “Theming”Role tokens only. Current: primary-colored indicator border and number, semibold label. Complete: filled primary indicator with a check, primary connector to the next step. Error: error-foreground indicator with an alert icon. Disabled: disabled text color. Forced-colors mode: CanvasText indicators and connectors, Highlight complete indicator, GrayText disabled steps.
Global configuration
Section titled “Global configuration”configureAbsoluteUI({ components: { stepper: { orientation: 'vertical', linear: false } } });Changes apply to existing instances immediately (the list is re-rendered).
Component configuration
Section titled “Component configuration”Instance options override global and theme defaults. All options are dynamic through update().
| Slot | Default | Used for |
|---|---|---|
icons.complete |
check |
Complete step indicator. |
icons.error |
alert-circle |
Error step indicator. |
Upcoming and current steps show their 1-based number. Icons resolve through the global icon provider/registry.
Security
Section titled “Security”Labels and descriptions use textContent. Each href is parsed with the URL standard against document.baseURI; only http:/https: and scheme-less URLs (relative, path-absolute, query, fragment) are accepted. javascript:, data:, and other schemes, including obfuscations such as java\tscript:, throw STEPPER_OPTIONS; the error names the property but never echoes the value. CSP-safe.
Data handling
Section titled “Data handling”No persistence, network access, or telemetry. status is consumer data; the stepper reads it and never changes it.
Performance
Section titled “Performance”One click listener on the list and one configuration subscription per instance. Every change rebuilds the step list (O(steps)); focus is restored to the same step. Budget: Stepper.create with 10 steps under 5 ms in headless Chromium.
Supported data scale
Section titled “Supported data scale”Designed for 2-7 steps; any count renders, but horizontal steps share the width, so long flows switch to the vertical layout early. Tested with 4-step flows.
Edge cases
Section titled “Edge cases”- Linear rule: a step after the current one is reachable only when every step before it, including the current step, has
status: 'complete'. An'error'step blocks forward reach past it. - Earlier steps are always reachable unless disabled, in both modes.
- The current step is shown as current even when its
statusis set; its status text is not rendered. - A disabled step can still be current (through
value, the default first step, orselect()). select()andupdate({ value })bypass reachability.- A single-step stepper has no reachable steps.
- A current or unreachable step with an
hrefrenders as text, not as a link. optionalshows a hint only; it does not change reachability.- Re-renders (
update(), configuration changes) rebuild the list but keep focus on the same step; a step that became current or unreachable is focused as text (tabindex="-1").
Errors
Section titled “Errors”| Code | Category | Cause |
|---|---|---|
STEPPER_OPTIONS |
configuration | Empty label; empty steps; an empty or duplicate step id; an empty step label; status other than 'complete'/'error'; an unsafe or unparsable href; value (or select(id)) not matching a step. property is stepper.<name>, for example stepper.steps.href. |
CFG_INVALID_VALUE |
configuration | Invalid orientation, linear, or direction (instance or global configuration). |
A failed construction leaves the host untouched: no children, classes, listeners, or subscriptions. A failed update() keeps the previous options and rendering.
Known limitations
Section titled “Known limitations”- The list is rebuilt on every change; focus inside the stepper is not restored after
update()or a configuration change. - After a link step is activated without
preventDefault(), focus is not moved (the page is expected to navigate). - The horizontal-to-vertical switch is fixed at a 30rem container width.
- No arrow-key navigation.
- Screen-reader behavior has not yet been manually verified with NVDA, JAWS, and VoiceOver.
Unsupported scenarios
Section titled “Unsupported scenarios”- HTML or custom nodes in labels and descriptions.
- Hrefs with schemes other than http and https.
- Branching or nested steps.
- Automatic completion tracking.