Stepper
Step-by-step progress indicator with expandable steps, substeps, and five visual variants. Compound component with context-based state management, roving tabIndex keyboard model, and a polite live region for active-step announcements.
Installation
dgadd is not public on npm yet. Until the first release, pack @diffgazer/add from the repository and install that tarball into this app, which is what puts dgadd on pnpm exec.
The hosted registry is not public yet because r.b4r7.dev does not resolve. Use this source checkout or a local registry preview until the endpoint returns 200.
Diffgazer packages are not yet published to npm. Until the first release, pack @diffgazer/ui and @diffgazer/keys from the repository and install those tarballs.
UI components require Tailwind CSS v4. Local copy mode imports src/styles/styles.css; package mode uses @diffgazer/ui CSS once packages are available.
Usage
Vertical variants
Five visual variants share the same compound API and render the same six
canonical states (pending · active · completed · error · skipped ·
disabled). Switch with variant on the root <Stepper>.
ascii. Default. Mono 1ch bracket glyphs ([x] [~] [ ] [!]) with a blinking cursor on the active step. Pure terminal idiom — matches dialog brackets, code-block chrome, and CLI output.numbered. 20px square with the CSS-counter step number; swaps to✓on completed. Use for form wizards and onboarding where the step number matters to the user.bullet. Single Unicode glyph per state (•·›···×) with a dashed connector. Minimal chrome — use when the stepper is one of many UI elements.tag. Uppercase text tag (DONE·RUN·WAIT·FAIL·SKIP·OFF) with a fixedmin-widthso the active state never shifts neighbour widths. Use for CI / build / deploy surfaces where users read status as text.progress. Unicode block-element bar (███ / █▌░ / ░░░) per step. Strong terminal flavor; best for short lists (3–5 steps) where granular per-step progress matters.
Six canonical states
For compact CI bars, wizard headers, and breadcrumb-style paths, use the separate Horizontal Stepper primitive.
More examples
Auto tone
With substeps
Controlled expansion
Error recovery
Keyboard navigation
API Reference
Stepper
StepperStep
StepperTrigger
StepperContent
StepperSubstep
Data attributes
Accessibility
Keyboard Navigation
Roving tabIndex: a single Tab key reaches the stepper. Arrow keys (Up/Down/Left/Right) move focus between enabled steps and skip disabled ones. Home/End jump to the first/last enabled step. Enter or Space toggles direct StepperContent.
| Key | Action |
|---|---|
| ArrowUp / ArrowLeft | Moves focus to the previous enabled StepperTrigger. |
| ArrowDown / ArrowRight | Moves focus to the next enabled StepperTrigger. |
| Home / End | Moves focus to the first or last enabled trigger. |
| Enter / Space | Toggles the focused step content when present. |
Controlled expansion with keyboard
Keyboard navigation
Notes
Visual variants (vertical)
Five variants: ascii (default — mono 1ch bracket glyphs with blinking active cursor), numbered (CSS-counter square with ✓ on completed), bullet (single glyph with dashed connector), tag (uppercase text tag, no width shift on active), progress (Unicode block-element bar per step). All variants render the same six canonical states.
Six canonical states
pending · active · completed · error · skipped · disabled. `skipped` ≠ `completed` (data integrity for form wizards). `disabled` ≠ `pending` (policy gate vs ordering). Skipped renders line-through. Disabled is non-interactive and skipped by arrow-key navigation.
State color
Completed indicators, labels, and connectors read the primary token, not success: finishing a step is neutral progress, so it stays monochrome in both themes. The status palette stays reserved for meaning — error is the only status that keeps a semantic hue.
Repainting from an app palette
An app that owns its own status palette (e.g. live-run telemetry colors) passes `indicatorClassName`/`labelClassName` on StepperTrigger instead of targeting the trigger's inner spans with descendant selectors. Both merge over the variant classes, so a single utility wins the conflict without copying the rest of the state styling.
Substep nesting
StepperContent stacks below its trigger and indents its children. Substeps draw a terminal branch rail from that indent (├─ per row, └─ on the last), so nested rows read as children of the step rather than a second column.
Keyboard model
Single roving tab stop on the active step (falls back to the first non-disabled step). Arrow keys (Up/Down/Left/Right) cycle focus and skip disabled steps. Home/End jump to first/last enabled step. Editable targets inside step content keep their native handling. The focused trigger shows the library's outside focus ring (2px --ring, 2px offset) around the whole row.
Expansion modes
Supports controlled (expandedIds + onExpandedChange) and uncontrolled (defaultExpandedIds) expansion. Multiple steps can be expanded simultaneously.
Composition contract
Keep StepperTrigger and StepperContent as explicit children of StepperStep. StepperStep only links aria-controls when it can see a direct StepperContent child; opaque wrappers that create content internally are not part of the current public contract.
Live region
Active-step transitions are announced via a persistent polite live region: 'Step {n} of {total}: {label}'. Label is sourced from the trigger's text content.
Source
Install via CLI: pnpm exec dgadd add ui/stepper. Keyboard hooks are included as standalone copies. For the full experience, use --integration keys.
Highlighted source loads after this disclosure opens. Browse the source repository.