# Stepper

> Stepper shows progress through a short, ordered flow and derives every step status from one active index.

Source: /docs/components/stepper

```tsx
import { Stepper } from '@aksornproject/aksara-ui'
```

## Basic stepper

`steps` is an array of `title` and optional `description`, in flow order. `activeStep` is the 0-based index of the current step. Earlier steps are complete and later ones are incomplete. Each step is a [Step](/docs/components/step) in a list item.

```tsx
import { Stepper } from '@aksornproject/aksara-ui'

const steps = [
  { title: 'Your details', description: 'Name and email' },
  { title: 'Address', description: 'Where to send it' },
  { title: 'Payment', description: 'Choose a method' },
]

export default function StepperBasic() {
  return (
    <Stepper
      aria-label="Enrolment progress"
      steps={steps}
      activeStep={1}
      className="w-full"
    />
  )
}
```

## Moving through the flow

Stepper holds no state. Keep the index in your own state and pass it as `activeStep`. An index past the last step makes every step complete.

```tsx
import { Button, Stepper } from '@aksornproject/aksara-ui'
import { useState } from 'react'

const steps = [
  { title: 'Your details', description: 'Name and email' },
  { title: 'Address', description: 'Where to send it' },
  { title: 'Payment', description: 'Choose a method' },
]

export default function StepperProgress() {
  const [active, setActive] = useState(0)

  return (
    <div className="flex w-full flex-col gap-spacing-16">
      <Stepper
        aria-label="Enrolment progress"
        steps={steps}
        activeStep={active}
      />
      <div className="flex gap-spacing-8">
        <Button
          type="button"
          variant="secondary-brand"
          disabled={active === 0}
          onClick={() => setActive((step) => Math.max(step - 1, 0))}
        >
          Back
        </Button>
        <Button
          type="button"
          disabled={active === steps.length}
          onClick={() => setActive((step) => Math.min(step + 1, steps.length))}
        >
          Continue
        </Button>
      </div>
    </div>
  )
}
```

## Not started and all complete

A negative `activeStep` means nothing has started. `activeStep="complete"` marks every step complete. Fractions truncate and `NaN` counts as `0`, so every number is a valid input.

```tsx
import { Stepper } from '@aksornproject/aksara-ui'

const steps = [
  { title: 'Your details', description: 'Name and email' },
  { title: 'Address', description: 'Where to send it' },
  { title: 'Payment', description: 'Choose a method' },
]

export default function StepperEnds() {
  return (
    <div className="flex w-full flex-col gap-spacing-24">
      <Stepper aria-label="Not started" steps={steps} activeStep={-1} />
      <Stepper aria-label="All complete" steps={steps} activeStep="complete" />
    </div>
  )
}
```

## Orientation and size

`orientation="vertical"` stacks the steps with an 8px gap. `size` sets `sm`, `md` or `lg` on every step. The defaults are `horizontal` and `sm`. Stepper never switches orientation by itself, so pick vertical for narrow screens.

```tsx
import { Stepper } from '@aksornproject/aksara-ui'

const steps = [
  { title: 'Your details', description: 'Name and email' },
  { title: 'Address', description: 'Where to send it' },
  { title: 'Payment', description: 'Choose a method' },
  { title: 'Review', description: 'Check and confirm' },
]

export default function StepperVertical() {
  return (
    <Stepper
      aria-label="Enrolment progress"
      steps={steps}
      activeStep={2}
      orientation="vertical"
      size="md"
      className="w-[320px]"
    />
  )
}
```

## Narrow containers

Horizontal steps share the width equally, with a 160px minimum each and a 16px gap. They never wrap. Below that width the list overflows, so the page owns the scrolling.

```tsx
import { Stepper } from '@aksornproject/aksara-ui'

const steps = [
  { title: 'Your details', description: 'Name and email' },
  { title: 'Address', description: 'Where to send it' },
  { title: 'Payment', description: 'Choose a method' },
  { title: 'Review', description: 'Check and confirm' },
]

export default function StepperOverflow() {
  return (
    <div className="w-[326px] overflow-x-auto">
      <Stepper aria-label="Enrolment progress" steps={steps} activeStep={1} />
    </div>
  )
}
```

## Custom styles

`indicator`, `titleClassName` and `descriptionClassName` apply to every step. Each takes one value for every status or a map keyed by status. Per-step differences need a hand-written list of `Step` parts.

```tsx
import { Stepper, typography } from '@aksornproject/aksara-ui'

const steps = [
  { title: 'Section A', description: 'Multiple choice' },
  { title: 'Section B', description: 'Short answers' },
  { title: 'Section C', description: 'Essay' },
]

export default function StepperCustom() {
  return (
    <Stepper
      aria-label="Exam sections"
      steps={steps}
      activeStep={1}
      titleClassName={{ complete: 'text-color-text-secondary' }}
      descriptionClassName={typography('supporting-regular')}
      className="w-full"
    />
  )
}
```

## Accessibility

- The root is an `ol` with `role="list"`, which keeps list semantics in Safari. Give it an `aria-label` when the page has more than one flow.
- Only the current step's list item has `aria-current="step"`.
- Complete and incomplete steps add visually hidden words after the title, "Completed" and "Not started" by default. Localise them with `statusLabels`.
- Indicators and segments are decorative and `aria-hidden`.
- Steps are not tabs, buttons or links. Put navigation controls outside the list and keep titles and descriptions noninteractive.

## API

### Stepper

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `steps` (required) | `{ title: StepText; description?: StepText }[]` |  | The steps, in flow order. Any length. |
| `activeStep` (required) | `number \| "complete"` |  | The current step, 0-based. Earlier steps are complete, this one is current and later ones are incomplete. `'complete'` marks every step complete, as does any index past the last step. A negative index means nothing has started. Fractions truncate and `NaN` is `0`. |
| `orientation` | `"horizontal" \| "vertical"` | `'horizontal'` | Defaults to `'horizontal'`. Narrow screens pick `'vertical'`; Stepper never switches by itself. |
| `size` | `"lg" \| "md" \| "sm"` | `'sm'` | Defaults to `'sm'`. Every step takes it. |
| `statusLabels` | `{ complete?: string; incomplete?: string }` |  | Screen-reader status words, merged over `{ complete: 'Completed', incomplete: 'Not started' }`. |
| `indicator` | `PerStatus<StepText>` |  | Custom disc content for every step. Replaces the number or the check. A map styles each status separately. |
| `titleClassName` | `PerStatus<string>` |  | Classes merged after the default title style, for every step. A map styles each status separately. |
| `descriptionClassName` | `PerStatus<string>` |  | Classes merged after the default description style, for every step. A map styles each status separately. |
| `className` | `string` |  | Merged last. The list fills its container and never wraps; the page owns horizontal overflow. |

### StepperItem

| Prop | Type | Description |
| --- | --- | --- |
| `title` (required) | `StepText` | The step label. Any node, so you can style parts of it inline. |
| `description` | `StepText` | A second line under the title. Any node, rendered only when given. |