Stepper
Stepper guides users through a multi-step flow. Each step renders an indicator
(status dot, status glyph, or custom content), an optional connecting rail, and
a title with an optional description. The active step is index-based and can be
controlled or uncontrolled.
Steps are real <button> elements: keyboard users activate them with Enter or
Space, arrow keys (plus Home/End) move focus along the list, and the
active step is announced through aria-current="step".
Usage
import { Stepper } from '@takeoff-ui/react-spar';
<Stepper>
<Stepper.Item>
<Stepper.Title />
<Stepper.Description />
</Stepper.Item>
</Stepper>
Playground
Step status
Steps before the active index show the completed treatment. Mark a step with
error or disabled; both apply as modifiers without replacing the derived
progress status. Disabled steps are natively disabled buttons — they are
unfocusable and unselectable.
Numbered indicators
Pass a number through Stepper.Item's indicator prop when the indicator
should show step numbers instead of the default status glyphs. indicator also
accepts a render function receiving the step's status and index — returning
undefined falls back to the built-in glyphs, so completed steps regain the
check.
Linear progression
With linear, users can revisit any previous step but only advance to the
immediate next step — and only while the current step is neither errored nor
disabled.
Clickability
Use isClickable={false} when a step should stay in the visual flow but not
change the active step on press.
Reverse
Use reverse to flip indicators and content along the cross axis without
changing the step order.
Vertical
Compact
mode="compact" drops the rails: each step carries a progress border that
recolors with its status.
Sizes
Accessibility
- The root is an ordered list; each step is a list item wrapping a real
<button>trigger, so steps are focusable and keyboard-activatable by default. - The active step's trigger carries
aria-current="step". - Steps that cannot change the active step (non-clickable or blocked by
linear) exposearia-disabledand stay silent on press;disabledsteps are natively disabled and removed from the tab order. - Indicators and rails are decorative and hidden from assistive technology; a
step's accessible name comes from its title text, extended with a visually
hidden status suffix on completed/errored steps — localize it through the
root's
completedLabel/errorLabelprops. Stepper.Descriptionis linked to the trigger througharia-describedbyinstead of inflating the accessible name.- Arrow keys along the stepper's orientation move focus between step triggers;
HomeandEndjump to the first and last focusable step.
API Reference
Stepper
Props
| Name | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode | - | Stepper.Item elements. The root derives each step’s index from its position in children. |
| active | number | - | Currently active step index (controlled). Not clamped: an out-of-range index renders every step completed (or inactive) with no active step. |
| defaultActive | number | 0 | Initially active step index (uncontrolled). |
| orientation | StepperOrientation | 'horizontal' | Layout axis of the step list. |
| mode | StepperMode | 'default' | Display mode — indicators with connecting rails ('default') or a progress border per step without rails ('compact'). |
| linear | boolean | false | Restricts navigation to a linear progression: any previous step, or the next step when the current one is neither errored nor disabled. |
| size | StepperSize | 'base' | Density scale for indicators and typography. |
| reverse | boolean | false | Flips indicators and content along the cross axis. |
| completedLabel | string | 'completed' | Accessible status suffix appended to a completed step's name — the check glyph alone is invisible to assistive technology. Localize per stepper; an empty string drops the suffix. |
| errorLabel | string | 'error' | Accessible status suffix appended to an errored step's name — the error glyph alone is invisible to assistive technology. Localize per stepper; an empty string drops the suffix. |
| classNames | Partial<Record<"root", string>> | - | Per-slot class name overrides. |
| slotProps | Partial<Record<"root", React.HTMLAttributes<HTMLElement>>> | - | Per-slot HTML attribute overrides. |
| className | string | - | Appends custom classes to the root slot of this part. |
Events
| Name | Type | Default | Description |
|---|---|---|---|
| onActiveChange | (index: number) => void | - | Fires with the new step index when the active step changes. |
| onStepClick | (detail: StepperStepClickDetail) => void | - | Fires on selectable step presses, and on the active step when pressed again, with the step's index and progress status. Disabled, non-clickable, and linear-blocked steps emit nothing. |
Data attributes
| Attribute | Applied when | Purpose |
|---|---|---|
| data-slot="root" | Always | Stable selector for wrapper styling on the root slot. |
| data-orientation | Always | Reflects the resolved orientation prop (horizontal | vertical). Emitted by the wrapper. |
| data-mode | Always | Reflects the resolved mode prop (default | compact). |
| data-size | Always | Reflects the resolved size prop so theme recipes can scope size variants. |
| data-linear | When linear is true. | Marks linear progression; selection gating itself is wrapper-owned. |
| data-reverse | When reverse is true. | Styling hook for the flipped indicator/content layout. |
Stepper.Item
Props
| Name | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode | - | Step content — typically Stepper.Title and Stepper.Description. Renders inside the step’s <button> trigger and provides its accessible name. |
| error | boolean | - | Marks the step as errored without changing its progress status. |
| disabled | boolean | - | Disables the step. The trigger renders as a natively disabled button: unfocusable, unselectable, and silent. |
| isClickable | boolean | true | Whether the step can be activated by pressing it. Non-clickable steps stay visible in the flow but are removed from the tab order. |
| indicator | React.ReactNode | ((state: StepperIndicatorState) => React.ReactNode) | - | Custom indicator content. Replaces the built-in status glyph (check, close, or dot) for every status except disabled. Pass a function to render by status — returning undefined or null falls back to the built-in glyphs, so numbered steps can surface the check once completed. |
| classNames | Partial<Record<StepperItemSlot, string>> | - | Per-slot class name overrides. |
| slotProps | Partial<Record<StepperItemSlot, React.HTMLAttributes<HTMLElement>>> | - | Per-slot HTML attribute overrides. |
| className | string | - | Appends custom classes to the root slot of this part. |
Data attributes
| Attribute | Applied when | Purpose |
|---|---|---|
| data-slot="root" | Always | Stable selector for wrapper styling on the root slot. |
| data-state | Always | Resolved progress status: inactive | active | completed. |
| data-error | When error is true. | Error treatment modifier; can coexist with any progress status. |
| data-disabled | When disabled is true. | Disabled treatment modifier; the trigger is natively disabled. |
| data-clickable | When pressing the step may change the active step — never on the active step itself. | Cursor/hover affordance hook. Respects disabled, isClickable, and linear gating. |
Stepper.Title
Data attributes
| Attribute | Applied when | Purpose |
|---|---|---|
| data-slot="root" | Always | Stable selector for wrapper styling on the root slot. |
Stepper.Description
Data attributes
| Attribute | Applied when | Purpose |
|---|---|---|
| data-slot="root" | Always | Stable selector for wrapper styling on the root slot. |
Type Definitions
| Name | Definition |
|---|---|
| StepperOrientation | 'horizontal' | 'vertical' |
| StepperMode | 'default' | 'compact' |
| StepperSize | 'large' | 'base' | 'small' | 'xsmall' |
| StepperStepClickDetail | { index: number; status: StepperStepStatus } |
| StepperIndicatorState | { status: StepperStepStatus; index: number } |
| StepperItemSlot | 'root' | 'trigger' | 'rail' | 'indicator' | 'content' |