Source: https://monarchui.dev/components/stepper/

Components · Workflow

# Stepper

Multi-step progress for wizards and onboarding: horizontal or vertical, current, complete, error and optional steps.

@rookcom/monarch-react/patterns

Component

## Wizard with clickable completed steps

## Usage examples

### Error and descriptions

### Vertical

### Compact

## Purpose

Shows where the user is in a fixed sequence of steps, what is done and what is left, and optionally lets them jump back to a finished step. Import from `@rookcom/monarch-react/patterns`.

### Use when

- A wizard or setup flow with three to seven named steps (create a community, import a file, connect a domain).
- A record that moves through ordered stages, shown read-only (a payroll run, a filing journey).

### Avoid when

- Sections the user can visit in any order: use Tabs.
- Progress of one task with no named steps: use Progress.
- A to-do list of independent tasks: use a checklist.

## Design principles

- Status is never colour alone: a check for complete, “!” for error, the number otherwise, plus hidden text (“completed”, “needs attention”).
- The current step is the strongest element: accent ring and semibold label.
- Connectors fill with the accent as steps complete, so progress reads at a glance.
- Navigation is opt-in: only with `onStepSelect` do reachable steps become buttons; the current and future steps never are.
- When there is no room it says less, not smaller: “Step 2 of 5 · Access” with a bar.

## API reference

```tsx
import { Stepper, stepStatus } from '@rookcom/monarch-react/patterns';
```

| Export | Kind | Rendering | Root class |
| --- | --- | --- | --- |
| `Stepper` | component | server-safe | `.mn-stepper` |
| `stepStatus` | utility | server-safe | – |

Renders `<nav aria-label="Progress">` (ref) with an `<ol>`; each step is an `<li>` with `data-status` and, for the current step, `aria-current="step"`.

`steps` are `{ id, label, description?, optional?, status?, disabled? }`; `status` overrides the derived state with `error` or `complete`.

`current` and `furthest` take a step id or a zero-based index. `stepStatus(index, currentIndex, step)` is exported to derive the same status elsewhere.

Generated from the TypeScript declarations of `packages/react/src/components/workflow/stepper`.

## Properties

### `Stepper`

Multi-step progress for wizards, onboarding and setup flows. An ordered list inside a `nav` with `aria-current="step"` on the current step; finished steps can be made clickable. Collapses to "Step 2 of 5" with a progress bar when there is no room.

| Prop | Type | Description |
| --- | --- | --- |
| `current` required | `string \| number` | The current step: its `id` or its zero-based index. Steps before it are complete. |
| `steps` required | `StepperStep[]` | The steps, in order. |
| `className` | `string` |  |
| `compact` | `"auto" \| "always" \| "never"` | When to show the compact "Step 2 of 5" summary with a progress bar instead of the full list. `auto` (default) switches when a horizontal stepper's own width is under 36rem, so it collapses at 320px and in narrow panels. |
| `furthest` | `string \| number` | The furthest step the user has reached (id or index), when they have gone back. Steps up to it stay clickable. Default: the current step. |
| `id` | `string` |  |
| `messages` | `Partial<StepperMessages>` | Override visible and announced strings. |
| `onStepSelect` | `((step: StepperStep, index: number) => void)` | Makes reachable steps (complete ones and any up to `furthest`) buttons that call this with the step and its index. Without it the stepper only shows progress. |
| `orientation` | `"horizontal" \| "vertical"` | Default `horizontal`. A vertical stepper suits a sidebar or a long setup checklist. |

## Variants

| Prop | Values | Description |
| --- | --- | --- |
| `orientation` | `'horizontal'``'vertical'` | `horizontal` (default) spreads steps across with connectors; `vertical` stacks them for sidebars and checklists, and never collapses. |
| `compact` | `'auto'``'always'``'never'` | `auto` (default) collapses a horizontal stepper to the “Step n of m” summary when its own width is under 36rem (a container query), so it adapts to 320px screens and narrow panels. |

## States

| State | Exposed as | Behaviour |
| --- | --- | --- |
| Current | `data-status="current", aria-current="step"` | Accent ring and number, semibold label. |
| Complete | `data-status="complete"` | Filled accent circle with a check; the following connector is accent. |
| Upcoming | `data-status="upcoming"` | Outlined circle with the number, muted label. |
| Error | `data-status="error"` | Filled danger circle with “!”, danger label. |
| Optional | – | “Optional” under the label (`messages.optional`). |
| Clickable | `button[data-clickable]` | Hover fill and underline; focus ring. |
| Disabled | `data-disabled` | A step that would be clickable but is disabled: disabled label colour. |

## Interaction behaviour

- Steps before `current` are complete. With `onStepSelect`, complete steps and steps up to `furthest` (after going back) are buttons that call it with the step and its index.
- The current step, future steps beyond `furthest` and `disabled` steps are plain text.
- The stepper does not move focus; on a step change, move focus to the new step’s heading in your page so screen-reader users hear where they are.

## Keyboard support

| Keys | Action |
| --- | --- |
| Tab | Moves between clickable steps (only reachable steps are buttons). |
| Enter / Space | Goes to the focused step. |

A read-only stepper has no tab stops.

## Accessibility

Role

navigation containing an ordered list

Accessible name

aria-label

on the nav, default “Progress”; name it after the flow (“Setup progress”).

- `aria-current="step"` marks the current step.
- Each step includes hidden text “Step n of m:” and, when relevant, “(completed)” or “(needs attention)”, so buttons are fully named.
- Indicator numbers, checks and the compact bar are `aria-hidden`; the text carries the information.
- In compact mode the list is not displayed and the summary text (“Step 2 of 5 Access”) is read instead.
- Verified with axe in jsdom and in Chromium, Firefox and WebKit on /components/stepper.

## Responsive behaviour

- Horizontal steps share the width; labels wrap rather than overflow.
- Under 36rem of its own width (`compact="auto"`) it shows “Step 2 of 5 · label” and a progress bar, so at 320px nothing is cramped.
- Vertical steppers keep the full list at every width.

## Theme support

- Monarch tokens only: `--mn-color-accent-solid` for complete and current, `--mn-color-danger-solid` for errors, `--mn-color-border-strong` for upcoming outlines.
- Follows the product accent; forced-colours mode draws indicators in Highlight.
- The hover transition is removed under `prefers-reduced-motion: reduce`.

## Do and don't

### Do

- Keep labels to one or two words, and keep the step count small (three to seven).
- Mark steps with problems `status: "error"` and jump the user to the first one on submit.
- Pair the stepper with Back / Continue buttons; the stepper is not the only way forward.

### Don't

- Do not make future steps clickable when earlier steps validate what they need.
- Do not use a stepper for tabs or for free-order sections.
- Do not rely on the step colour to explain an error; say what is wrong in the step itself.

## Related components

- [Progress](https://monarchui.dev/components/progress-bar/): Progress of one task without named steps; also the bar in compact mode.
- [Tabs](https://monarchui.dev/components/tabs/): Sections in free order.
- [OnboardingChecklist](https://monarchui.dev/components/onboarding-checklist/): Independent tasks the user ticks off in any order.

## Dependencies

| Kind | Depends on |
| --- | --- |
| npm packages | `react` |
| Monarch components | [Progress](https://monarchui.dev/components/progress-bar/) |

## Testing coverage

- Named nav and ordered list; `aria-current="step"`; complete/current/upcoming statuses; hidden “Step n of m” and status text; description and optional note; axe.
- Current by index (clamped) and by id.
- Error and out-of-order complete overrides; axe.
- No buttons without `onStepSelect`; with it only completed steps are buttons, clicked or activated with Enter; axe.
- `furthest` keeps later reached steps clickable after going back; disabled steps are not.
- Compact summary with an aria-hidden bar; vertical orientation.
- Translated messages; `stepStatus`; renders on the server.

| Check | Covered |
| --- | --- |
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery/stepper; Cross-browser axe: /components/stepper |

Tests: `packages/react/src/components/workflow/stepper`

## All example code

### Wizard with clickable completed steps

```tsx
import { useState } from 'react';
import { Button } from '@rookcom/monarch-react';
import { Stepper, type StepperStep } from '@rookcom/monarch-react/patterns';

const SETUP: StepperStep[] = [
  { id: 'basics', label: 'Basics' },
  { id: 'access', label: 'Access' },
  { id: 'branding', label: 'Branding', optional: true },
  { id: 'review', label: 'Review' },
];

function WizardDemo() {
  const [current, setCurrent] = useState(1);
  const [furthest, setFurthest] = useState(1);
  const go = (i: number) => {
    setCurrent(i);
    setFurthest((f) => Math.max(f, i));
  };
  return (
    <div style={{ display: 'grid', gap: 16, width: '100%' }}>
      <Stepper
        aria-label="Setup progress"
        steps={SETUP}
        current={current}
        furthest={furthest}
        onStepSelect={(_, i) => go(i)}
      />
      <div style={{ display: 'flex', gap: 8 }}>
        <Button disabled={current === 0} onClick={() => go(current - 1)}>
          Back
        </Button>
        <Button
          variant="primary"
          disabled={current === SETUP.length - 1}
          onClick={() => go(current + 1)}
        >
          Continue
        </Button>
      </div>
    </div>
  );
}

<WizardDemo />
```

### Error and descriptions

```tsx
import { Stepper } from '@rookcom/monarch-react/patterns';

<Stepper
  aria-label="Import progress"
  current="map"
  steps={[
    { id: 'upload', label: 'Upload', description: 'contacts.csv' },
    {
      id: 'map',
      label: 'Map columns',
      description: '2 columns need a field',
      status: 'error',
    },
    { id: 'preview', label: 'Preview' },
    { id: 'import', label: 'Import' },
  ]}
/>
```

### Vertical

```tsx
import { Stepper } from '@rookcom/monarch-react/patterns';

<Stepper
  orientation="vertical"
  aria-label="Domain setup"
  current={2}
  steps={[
    { id: 'add', label: 'Add domain', description: 'help.example.com' },
    { id: 'dns', label: 'Add DNS records' },
    { id: 'verify', label: 'Verify', description: 'Checking every few minutes' },
    { id: 'live', label: 'Live' },
  ]}
/>
```

### Compact

```tsx
import { Stepper, type StepperStep } from '@rookcom/monarch-react/patterns';

const SETUP: StepperStep[] = [
  { id: 'basics', label: 'Basics' },
  { id: 'access', label: 'Access' },
  { id: 'branding', label: 'Branding', optional: true },
  { id: 'review', label: 'Review' },
];

<Stepper aria-label="Checkout progress" compact="always" current={1} steps={SETUP} />
```
