# Stepper

Package: @commenda/pathfinder@0.1.0
Page: /docs/components/stepper
Figma: https://www.figma.com/design/dn7BkGYEfIyP0o2zfesfVh/The-System?node-id=676-2

```tsx
import { Stepper, type StepperItem } from '@commenda/pathfinder/stepper';

const filingSteps: StepperItem[] = [
  {
    id: 'compose',
    label: 'Compose',
    description: 'Write the title and message',
    state: 'done',
  },
  {
    id: 'audience',
    label: 'Audience',
    description: 'Who sees this alert',
    state: 'current',
  },
  {
    id: 'schedule',
    label: 'Schedule',
    description: 'When it goes live',
    state: 'upcoming',
  },
];

export const StepperDefault = () => (
  <Stepper label="Create alert" items={filingSteps} />
);
```


## Installation [#installation]

```sh
pnpm --filter your-app add "@commenda/pathfinder@workspace:*"
```

Import `@commenda/pathfinder/styles.css` once after host CSS. Pathfinder ships compiled, prefixed Tailwind v4 CSS; the application needs no Tailwind scanning. Import from `@commenda/pathfinder/stepper`. Interactive callbacks belong in a client component in Next.js App Router.

## API reference [#api-reference]

| Prop | Type | Requirement | Behavior |
| --- | --- | --- | --- |
| `label` | `string` | required | Required; ordered-list name |
| `items` | `readonly StepperItem[]` | required | Required; id, label, state, optional description |
| `orientation` | `"vertical" \| "horizontal"` | optional | horizontal |
| `navigationConfig` | `{ onStepChange: (id: string) => void; }` | optional | Optional; only done steps request navigation |
| `stateLabels` | `Partial<Record<"done" \| "disabled" \| "upcoming" \| "current", string>>` | optional | Localized Upcoming, Current step, Completed, Unavailable |
| `id` | `string` | optional | Stable HTML identifier for the component. |
| `className` | `string` | optional | Additional classes for the component root. |

### navigationConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `onStepChange` | `(id: string) => void` | Yes |  |

Root IDs, className, appropriate ARIA attributes, and typed refs are supported. Item IDs/values must be unique and stable.

## Usage rules [#usage-rules]

Each step explicitly declares `state: upcoming | current | done | disabled`. At most one is current; a finished flow can have none. The component does not duplicate that state with a separate current index.

Done steps become native buttons when navigationConfig is supplied. Upcoming and disabled steps do not navigate. The application owns validation, retained answers, Back/Continue/Submit actions, and focus after navigation. A failed field remains a field error; it does not create a red step marker. Keep later completed steps done when revisiting earlier answers unless those answers invalidate them.

Prefer three to six steps on a full page. Use the vertical layout when a narrow container cannot fit clear horizontal labels. The page owns sticky positioning. The Navigation example illustrates navigation only, not a production validation engine.

## Examples [#examples]

### Vertical [#vertical]


```tsx
import { Stepper, type StepperItem } from '@commenda/pathfinder/stepper';

const filingSteps: StepperItem[] = [
  {
    id: 'compose',
    label: 'Compose',
    description: 'Write the title and message',
    state: 'done',
  },
  {
    id: 'audience',
    label: 'Audience',
    description: 'Who sees this alert',
    state: 'current',
  },
  {
    id: 'schedule',
    label: 'Schedule',
    description: 'When it goes live',
    state: 'upcoming',
  },
];

export const StepperVertical = () => (
  <Stepper
    label="Create alert"
    orientation="vertical"
    items={[
      ...filingSteps,
      {
        id: 'review',
        label: 'Review',
        state: 'disabled',
        description: 'Available after scheduling',
      },
    ]}
  />
);
```


### Navigation [#navigation]


```tsx
import { useState } from 'react';

import { Button } from '@commenda/pathfinder/button';
import { Stepper, type StepperItem } from '@commenda/pathfinder/stepper';

const filingSteps: StepperItem[] = [
  {
    id: 'compose',
    label: 'Compose',
    description: 'Write the title and message',
    state: 'done',
  },
  {
    id: 'audience',
    label: 'Audience',
    description: 'Who sees this alert',
    state: 'current',
  },
  {
    id: 'schedule',
    label: 'Schedule',
    description: 'When it goes live',
    state: 'upcoming',
  },
];

export const StepperNavigation = () => {
  const [current, setCurrent] = useState(0);
  const [completed, setCompleted] = useState<readonly string[]>([]);

  return (
    <div style={{ display: 'grid', gap: 24 }}>
      <Stepper
        label="Create alert"
        items={filingSteps.map((step, index) => ({
          ...step,
          state:
            index === current
              ? 'current'
              : completed.includes(step.id)
                ? 'done'
                : 'upcoming',
        }))}
        navigationConfig={{
          onStepChange: (id) => {
            setCurrent(filingSteps.findIndex((step) => step.id === id));
          },
        }}
      />
      <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
        <Button
          variant="secondary"
          disabled={current === 0}
          onClick={() => {
            setCurrent(Math.max(0, current - 1));
          }}
        >
          Back
        </Button>
        <Button
          disabled={current === filingSteps.length}
          onClick={() => {
            const step = filingSteps[current];
            if (current < filingSteps.length)
              setCompleted([...new Set([...completed, step.id])]);
            setCurrent(Math.min(filingSteps.length, current + 1));
          }}
        >
          {current < filingSteps.length - 1
            ? 'Continue'
            : current === filingSteps.length
              ? 'Published'
              : 'Publish'}
        </Button>
      </div>
    </div>
  );
};
```


## Design rules [#design-rules]

Figma guidance and reviewed implementation decisions for this component. Rule IDs remain stable for source comments and AI references.

### Stepper [#stepper]

Source: [676:104](https://www.figma.com/design/dn7BkGYEfIyP0o2zfesfVh/The-System?node-id=676-104), items 676:61, behavior 676:35169, guidance 676:35190.

- `STEPPER-01` — Source/Pathfinder: explicit upcoming/current/done/disabled states, at most one current. Only done steps with navigationConfig become buttons; callbacks request navigation without changing caller-owned state. Current exposes aria-current=step.
- `STEPPER-02` — Source: current-step validation, saved answers, footer buttons, and focus belong to the application. Field errors never invent a red marker. Final primary action says Publish, Submit, or Create. Keep completed steps when revisiting unless the application invalidates them.
- `STEPPER-03` — Source: three to six steps on full pages. Short labels, optional descriptions. Parent owns sticky positioning and responsive choice of horizontal/vertical layout.
- `STEPPER-04` — Source: 20 markers, upcoming pearl-300/900, current blue-700/white, done green-700/check, disabled pearl-100/600. Horizontal text gap 8, connector at marker center with 12 side gaps. Vertical 240, padding 8, gap 12, row gap 2, radius 8. Weight never shifts; completion enters without bounce and never animates on initial mount.
