# Stepper

> Package `@umriss-ui/core`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/core/stepper/>. Every page in one line: [llms.txt](https://romanhaendler.github.io/umriss-ui/core/llms.txt); every page in full: [llms-full.txt](https://romanhaendler.github.io/umriss-ui/core/llms-full.txt).

Shows how far a procedure has come: which steps are done, which one is being worked on, which lie ahead and which failed (also called a progress tracker). Reach for it for approvals, rollouts and closing routines.

```ts
import { Stepper } from "@umriss-ui/core";
```

Demo page: https://romanhaendler.github.io/umriss-ui/core/stepper/

Each state is said as a word as well as drawn, so a screen reader hears "done", "current step", "upcoming" or "failed". A failed step stays failed when the procedure moves past it; past the last step, every step is done.

## Examples

### Where a procedure stands

Pass the `steps` in order and the index of the `current` one; the steps before it read as done, the ones after as upcoming.

```tsx
import { Stepper } from "@umriss-ui/core";

export default function WhereAProcedureStands() {
  return (
    <Stepper
      aria-label="Incident INC-1048"
      steps={[{ label: "Detect" }, { label: "Acknowledge" }, { label: "Mitigate" }, { label: "Resolve" }, { label: "Review" }]}
      current={2}
      style={{ maxWidth: 720 }}
    />
  );
}
```

### Steps in a column

Set `orientation` to `vertical` where each step needs a `description` beneath its label, such as a deadline.

```tsx
import { Stepper } from "@umriss-ui/core";

const CLOSE = [
  { label: "Post all invoices", description: "Until Tuesday, 3 March" },
  { label: "Reconcile accounts", description: "Bank, payables, receivables" },
  { label: "Book accruals", description: "Bonuses and open orders" },
  { label: "Report to the board", description: "Friday, 6 March" },
];

export default function StepsInAColumn() {
  return <Stepper aria-label="Closing February" orientation="vertical" steps={CLOSE} current={2} style={{ maxWidth: 360 }} />;
}
```

### A failed step

Mark a step `failed` and it shows a cross and says "failed"; it stays failed when the procedure has moved past it.

```tsx
import { Stepper } from "@umriss-ui/core";

export default function AFailedStep() {
  return (
    <Stepper
      aria-label="Shipment FP-1004249"
      orientation="vertical"
      steps={[
        { label: "Loaded", description: "North depot, 06:40" },
        { label: "Out for delivery", description: "Tour T-01" },
        { label: "First attempt", description: "10:35, nobody at the door", failed: true },
        { label: "Second attempt", description: "Tomorrow, 08:00–10:00" },
        { label: "Delivered" },
      ]}
      current={3}
      style={{ maxWidth: 360 }}
    />
  );
}
```

### Move on yourself

The stepper has no keys of its own: your application sets `current` after its own check, here behind two buttons.

```tsx
import { useState } from "react";
import { Button, Stack, Stepper } from "@umriss-ui/core";

const STEPS = [{ label: "Choose people" }, { label: "Set capacity" }, { label: "Plan leave" }, { label: "Confirm" }];

export default function MoveOnYourself() {
  const [current, setCurrent] = useState(1);

  return (
    <Stack gap={4} style={{ maxWidth: 560 }}>
      <Stepper aria-label="Set up sprint 15" steps={STEPS} current={current} />
      <Stack direction="row" gap={2}>
        <Button size="sm" variant="secondary" disabled={current === 0} onClick={() => setCurrent(current - 1)}>
          Back
        </Button>
        <Button size="sm" variant="primary" disabled={current === STEPS.length} onClick={() => setCurrent(current + 1)}>
          {current >= STEPS.length - 1 ? "Start the sprint" : "Next"}
        </Button>
      </Stack>
    </Stack>
  );
}
```

### A deployment

At a card's head the stepper shows where a rollout stands; a failed step is marked in place and an alert offers the way on.

```tsx
import { useState } from "react";
import { Alert, Button, Card, CardBody, CardHeader, ProgressBar, Stack, Stepper, Text } from "@umriss-ui/core";

const STEPS = [
  { label: "Build" },
  { label: "Test" },
  { label: "Canary", description: "5 % of traffic, 15 minutes" },
  { label: "Roll out", description: "All regions" },
  { label: "Verify" },
];

export default function ADeployment() {
  const [failed, setFailed] = useState(false);
  const current = 2;
  const steps = STEPS.map((step, index) => (index === current && failed ? { ...step, failed: true } : step));

  return (
    <Card style={{ maxWidth: 720 }}>
      <CardHeader
        eyebrow="Checkout · release 2026.03.17-2"
        title="Cap payment retries at one"
        actions={
          <Button size="sm" variant="ghost" onClick={() => setFailed(true)} disabled={failed}>
            Fail the canary
          </Button>
        }
      />
      <CardBody>
        <Stack gap={5}>
          <Stepper aria-label="Deployment" steps={steps} current={current} />
          {failed ? (
            <Alert
              tone="danger"
              title="Canary stopped"
              actions={
                <Button size="sm" variant="secondary" onClick={() => setFailed(false)}>
                  Retry the canary
                </Button>
              }
            >
              The error rate on the canary rose to 2.4 %, above the 1 % the rollout allows.
            </Alert>
          ) : (
            <Stack gap={2}>
              <Text size="sm">Canary on 5 % of traffic - 6 of 15 minutes</Text>
              <ProgressBar value={0.4} label="Canary" valueText="6 of 15 minutes" />
            </Stack>
          )}
        </Stack>
      </CardBody>
    </Card>
  );
}
```

## When to use something else

- Progress as a share of a known amount, without named steps → ProgressBar
- Where a page sits in a hierarchy → Breadcrumb

## Keyboard

The keys of [Button](https://romanhaendler.github.io/umriss-ui/core/button/#keyboard-button) apply here.

## Accessibility

An ordered list, so a screen reader says how many steps there are and which one it reads. The current step carries `aria-current="step"`; every other step's state follows its label as a word only a screen reader hears, and the drawn number, tick or cross is hidden from it.

It announces nothing when `current` moves on: a reader hears the new state where it reads the list. A step that must be heard as it fails is said in an [Alert](https://romanhaendler.github.io/umriss-ui/core/alert/); a share of a step's work stands in a [ProgressBar](https://romanhaendler.github.io/umriss-ui/core/progressbar/), as in the deployment.

Under forced colours the lines between the steps take the system's text colour, and the current step's marker an outline in the selection colour.

## API

### `StepperProps`

| Prop | Type | Default | Description |
|---|---|---|---|
| `steps` *required* | `readonly `[`StepperStep`](https://romanhaendler.github.io/umriss-ui/core/stepper/#type-StepperStep)`[]` | — | The steps in the order they are walked.<br>Shown in: [Where a procedure stands](https://romanhaendler.github.io/umriss-ui/core/stepper/#where-a-procedure-stands), [Steps in a column](https://romanhaendler.github.io/umriss-ui/core/stepper/#steps-in-a-column), [A failed step](https://romanhaendler.github.io/umriss-ui/core/stepper/#a-failed-step) and 6 more |
| `current` *required* | `number` | — | The index of the step being worked on. The steps before it are done, the ones after it upcoming; past the last step, every step is done.<br>Shown in: [Where a procedure stands](https://romanhaendler.github.io/umriss-ui/core/stepper/#where-a-procedure-stands), [Steps in a column](https://romanhaendler.github.io/umriss-ui/core/stepper/#steps-in-a-column), [A failed step](https://romanhaendler.github.io/umriss-ui/core/stepper/#a-failed-step) and 6 more |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | `horizontal` in a row, `vertical` in a column for steps with longer descriptions.<br>Shown in: [Steps in a column](https://romanhaendler.github.io/umriss-ui/core/stepper/#steps-in-a-column), [A failed step](https://romanhaendler.github.io/umriss-ui/core/stepper/#a-failed-step), [An invoice in tabs (Tabs)](https://romanhaendler.github.io/umriss-ui/core/tabs/#an-invoice-in-tabs) |
| `ref` | `Ref<HTMLOListElement>` | — | Receives the component's root element. From `RefProp`.<br>Shown in: [A row that holds still (Sizes)](https://romanhaendler.github.io/umriss-ui/core/sizes/#a-row-that-holds-still), [An anchored surface (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#anchored-surface), [Take the width of the trigger (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#width-from-anchor) and 2 more |

Also takes every attribute of the rendered element.

### `StepperStep`

| Prop | Type | Default | Description |
|---|---|---|---|
| `label` *required* | `ReactNode` | — | What the step is called - a verb for what happens in it.<br>Shown in: [Where a procedure stands](https://romanhaendler.github.io/umriss-ui/core/stepper/#where-a-procedure-stands), [A failed step](https://romanhaendler.github.io/umriss-ui/core/stepper/#a-failed-step), [Import budgets (FileInput)](https://romanhaendler.github.io/umriss-ui/core/fileinput/#import-budgets) and 1 more |
| `description` | `ReactNode` | — | A line beneath the label - how long the step takes, what it needs.<br>Shown in: [A failed step](https://romanhaendler.github.io/umriss-ui/core/stepper/#a-failed-step), [An invoice in tabs (Tabs)](https://romanhaendler.github.io/umriss-ui/core/tabs/#an-invoice-in-tabs) |
| `failed` | `boolean` | — | The step failed. It says so whether it is behind the current step or is the current one: a failed step is not done.<br>Shown in: [A failed step](https://romanhaendler.github.io/umriss-ui/core/stepper/#a-failed-step), [An invoice in tabs (Tabs)](https://romanhaendler.github.io/umriss-ui/core/tabs/#an-invoice-in-tabs) |

## Known limits

- The steps are read, not navigated: no keys, no clicks, and no next or back of its own. Moving on is your application's decision.
- No branching or optional steps; pass the steps that apply.

What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
