# Step Dots

> A row of dots that shows progress through steps, like an onboarding flow, with each step named for screen readers.

- Name: `step-dots`
- Status: beta
- Categories: navigation
- File: the install writes `step-dots.tsx` to your `ui` alias, `@/components/ui` by default.
- Gallery: https://arbokui.com/components/step-dots
- Registry item: https://arbokui.com/r/step-dots.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @arbokui/step-dots
   ```

## Use it for

- Showing where someone is in a short flow with a few steps: onboarding, a setup wizard, a checkout.
- Pass one name per step. Sighted people see dots, and screen readers read each name, its place, and whether it is done.
- Set jump='back' to let people click a done step to go back to it. Set jump='any' when every step can be done in any order.
- Set current to steps.length when the flow is finished, so every dot shows as done.
- For another language, pass aria-label, stepText, and stateText.

## Avoid it for

- Flows where people need to read the step names. Dots show no text, so use a list or tabs with visible labels.
- More than about eight steps. The dots get hard to count. Show 'Step 3 of 12' as text instead.
- Paging through a carousel of slides. Dots here mean progress, and done steps fill in.
- Moving focus for the app. A jump only changes the current step. Your app shows that step and moves focus to it.

## Props

- `steps`: `readonly string[]`. One name per step, in order. Screen readers read each name with 'Step 2 of 4' and its state.
- `current`: `number`. The current step, counted from 0. Steps before it are done. Set it to steps.length when all are done. Use it with onCurrentChange.
- `defaultCurrent`: `number`. Default `0`. The first current step when you do not control it.
- `onCurrentChange`: `(index: number) => void`. Called with the step someone clicked. Clicking the current step does not call it.
- `jump`: `"none" | "back" | "any"`. Default `"none"`. Which dots are buttons. back: done steps and the current one. any: every step. none: no buttons.
- `tone`: `"neutral" | "brand" | "danger" | "success" | "warning"`. Default `"neutral"`. Color of the done and current dots.
- `aria-label`: `string`. Default `"Progress"`. Names the list. Pass a better name, like 'Setup progress'. With aria-labelledby set, the default is dropped.
- `stepText`: `string`. Default `"Step {step} of {total}: {name}, {state}"`. What screen readers hear for each step. {step} is its place, {total} the number of steps, {name} its name, and {state} a word from stateText.
- `stateText`: `{ done: string; current: string; upcoming: string }`. Default `{ done: "done", current: "current", upcoming: "upcoming" }`. The words for {state} in stepText.
- `className`: `string`. Extra classes for the list. Later classes win, so className='\[--step-dots-size:6px\]' makes the dots smaller.

## Accessibility

- Renders an \<ol>, so screen readers say how many steps there are.
- Each step has hidden text, like 'Step 2 of 4: Profile, current'. stepText and stateText change the words. The dots themselves are hidden from screen readers.
- The current step has aria-current="step".
- With jump on, a clickable step is a native \<button>, so Tab, Enter, and Space work. Steps people cannot reach are not buttons.
- The current step stays a button while jump is on, so focus stays on the dot someone pressed when it becomes current.
- Each dot sits in a cell at least 24px square, the smallest click target WCAG 2.2 allows.
- The current dot is a wider pill, so it is not marked by color alone. Done dots use the tone fill and upcoming dots use --input. The theme tests hold both to 3:1 on the page and on cards.
- The pill grows and shrinks over --motion-duration-base. data-motion="off" makes it instant. Reduced motion keeps the color change and drops the change of width.

## Examples

### Onboarding progress

```tsx
<StepDots
  aria-label="Setup progress"
  steps={["Account", "Profile", "Team", "Invite"]}
  current={1}
/>
```

### Go back to a done step

```tsx
"use client";
import { useState } from "react";

const steps = ["Cart", "Shipping", "Payment", "Review"];

function CheckoutSteps() {
  const [step, setStep] = useState(2);
  return (
    <StepDots
      aria-label="Checkout progress"
      steps={steps}
      current={step}
      onCurrentChange={setStep}
      jump="back"
      tone="brand"
    />
  );
}
```

### Every step done

```tsx
const steps = ["Account", "Profile", "Team"];

<StepDots aria-label="Setup progress" steps={steps} current={steps.length} tone="success" />
```

### In another language

```tsx
<StepDots
  aria-label="Fortschritt der Einrichtung"
  steps={["Konto", "Profil", "Team", "Einladen"]}
  current={1}
  stepText="Schritt {step} von {total}: {name}, {state}"
  stateText={{ done: "erledigt", current: "aktuell", upcoming: "offen" }}
/>
```
