---
title: Stepper
description: Tab-structured step navigation with configurable orientation and separator layout.
sidebar:
  order: 3
search:
  tags: [wizard, steps, progress, workflow]
---

# Stepper

> Tab-structured step navigation with configurable orientation and separator layout.

## Import

```tsx
import { Stepper } from 'moraine'
```

## Slot Structure

Header with step items and optional per-step content panels.

```text
root
├── header
│   └── item (×n)
│       ├── container
│       │   ├── trigger
│       │   └── separator (optional, between items)
│       └── wrapper (optional)
│           ├── title (optional)
│           └── description (optional)
└── content (×n, optional)
```

## Examples

### Sizes

Preview the Stepper across all supported sizes using the default linear, non-clickable tab navigation.

```tsx
function Sizes() {
  const createCheckoutSteps = () => [
    {
      title: 'Address',
      description: 'Where should we send the order?',
      icon: 'i-lucide:map-pinned',
      value: 'address',
      content: <p class="text-sm text-foreground">Collect shipping address details.</p>,
    },
    {
      title: 'Shipping',
      description: 'Choose a delivery method.',
      icon: 'i-lucide:truck',
      value: 'shipping',
      content: <p class="text-sm text-foreground">Pick standard, express, or local pickup.</p>,
    },
    {
      title: 'Payment',
      description: 'Confirm billing and payment.',
      icon: 'i-lucide:credit-card',
      value: 'payment',
      content: <p class="text-sm text-foreground">Review billing details and submit payment.</p>,
    },
  ]

  const STEPPER_SIZES = ['xs', 'sm', 'md', 'lg', 'xl'] as const

  return (
    <div class="space-y-6">
      <For each={STEPPER_SIZES}>
        {(size) => (
          <div class="space-y-2">
            <p class="text-xs text-muted-foreground tracking-wide font-medium uppercase">{size}</p>
            <Stepper items={createCheckoutSteps()} defaultValue="shipping" size={size} />
          </div>
        )}
      </For>
    </div>
  )
}
```

### Controlled + Non-linear

Manage the active step externally and allow jumping to any step.

```tsx
function ControlledNonLinear() {
  const RELEASE_STEPS = () => [
    {
      title: 'Draft',
      value: 'draft',
      content: <p class="text-sm text-foreground">Prepare release notes.</p>,
    },
    {
      title: 'Review',
      value: 'review',
      content: <p class="text-sm text-foreground">Collect team approvals.</p>,
    },
    {
      title: 'Ship',
      value: 'ship',
      content: <p class="text-sm text-foreground">Deploy to production.</p>,
    },
  ]

  const [releaseStep, setReleaseStep] = createSignal('review')

  return (
    <div class="space-y-4">
      <Stepper
        items={RELEASE_STEPS()}
        value={releaseStep()}
        onChange={setReleaseStep}
        linear={false}
      />
      <div class="flex flex-wrap gap-2 items-center">
        <Button size="sm" variant="outline" onClick={() => setReleaseStep('draft')}>
          Go to draft
        </Button>
        <Button size="sm" variant="outline" onClick={() => setReleaseStep('review')}>
          Go to review
        </Button>
        <Button size="sm" variant="outline" onClick={() => setReleaseStep('ship')}>
          Go to ship
        </Button>
        <p class="text-xs text-muted-foreground">Current step: {releaseStep()}</p>
      </div>
    </div>
  )
}
```

### Clickable vs Non-Clickable

Compare the default non-clickable mode with explicit click-enabled navigation.

```tsx
function ClickableVsReadOnly() {
  const createCheckoutSteps = () => [
    {
      title: 'Address',
      description: 'Where should we send the order?',
      icon: 'i-lucide:map-pinned',
      value: 'address',
      content: <p class="text-sm text-foreground">Collect shipping address details.</p>,
    },
    {
      title: 'Shipping',
      description: 'Choose a delivery method.',
      icon: 'i-lucide:truck',
      value: 'shipping',
      content: <p class="text-sm text-foreground">Pick standard, express, or local pickup.</p>,
    },
    {
      title: 'Payment',
      description: 'Confirm billing and payment.',
      icon: 'i-lucide:credit-card',
      value: 'payment',
      content: <p class="text-sm text-foreground">Review billing details and submit payment.</p>,
    },
  ]

  return (
    <div class="space-y-6">
      <div class="space-y-2">
        <p class="text-xs text-muted-foreground tracking-wide font-medium uppercase">
          Default (`linear=true`, `clickable=false`)
        </p>
        <Stepper items={createCheckoutSteps()} defaultValue="address" />
      </div>

      <div class="space-y-2">
        <p class="text-xs text-muted-foreground tracking-wide font-medium uppercase">
          Click enabled (`linear=true`, `clickable=true`)
        </p>
        <Stepper items={createCheckoutSteps()} defaultValue="address" clickable />
      </div>

      <div class="space-y-2">
        <p class="text-xs text-muted-foreground tracking-wide font-medium uppercase">
          Non-linear (`linear=false`, `clickable=true`)
        </p>
        <Stepper items={createCheckoutSteps()} defaultValue="address" linear={false} clickable />
      </div>
    </div>
  )
}
```

### Linear Checkout

Enable clicking while still only allowing the next available step to be selected.

```tsx
function LinearCheckout() {
  const createCheckoutSteps = () => [
    {
      title: 'Address',
      description: 'Where should we send the order?',
      icon: 'i-lucide:map-pinned',
      value: 'address',
      content: <p class="text-sm text-foreground">Collect shipping address details.</p>,
    },
    {
      title: 'Shipping',
      description: 'Choose a delivery method.',
      icon: 'i-lucide:truck',
      value: 'shipping',
      content: <p class="text-sm text-foreground">Pick standard, express, or local pickup.</p>,
    },
    {
      title: 'Payment',
      description: 'Confirm billing and payment.',
      icon: 'i-lucide:credit-card',
      value: 'payment',
      content: <p class="text-sm text-foreground">Review billing details and submit payment.</p>,
    },
  ]

  return <Stepper items={createCheckoutSteps()} defaultValue="address" clickable />
}
```

### Vertical

Render the tab-structured step navigation vertically.

```tsx
function Vertical() {
  const PIPELINE_STEPS = () => [
    {
      title: 'Queued',
      description: 'Waiting for worker capacity.',
      value: 'queued',
      content: <p class="text-sm text-foreground">This job is waiting in the queue.</p>,
    },
    {
      title: 'Building',
      description: 'Compiling and bundling assets.',
      value: 'building',
      content: <p class="text-sm text-foreground">The current build is running.</p>,
    },
    {
      title: 'Ready',
      description: 'Artifacts are available.',
      value: 'ready',
      content: <p class="text-sm text-foreground">The deployment artifact is ready to use.</p>,
    },
  ]

  return (
    <div class="max-w-3xl">
      <Stepper items={PIPELINE_STEPS()} orientation="vertical" defaultValue="building" />
    </div>
  )
}
```

## API Reference

### Attributes

#### `root`

Stepper container that owns orientation, step state, and panel rendering.

#### `header`

Step navigation header that contains all step triggers.

##### ARIA Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| aria-orientation | boolean \| string \| undefined | Communicates horizontal or vertical orientation. |
| role | string | Defines the semantic role exposed to assistive technology. |

#### `item`

Wrapper for one step trigger.

##### Data Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| data-disabled | string \| undefined | Present when the component or item is disabled. |
| data-state | string \| undefined | Stores the component state used by styling hooks. |

#### `container`

Text column inside a step trigger.

#### `trigger`

Interactive step control users activate to select a step.

##### Data Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| data-clickable | string \| undefined | State or slot attribute exposed for styling hooks and selectors. |
| data-selected | string \| undefined | Present when the item is selected. |
| data-state | string \| undefined | Stores the component state used by styling hooks. |

##### ARIA Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| aria-controls | boolean \| string \| undefined | References the controlled element while the related content is mounted. |
| aria-describedby | boolean \| string \| undefined | References descriptive text associated with the control. |
| aria-labelledby | boolean \| string \| undefined | References the element that labels the control or region. |
| aria-selected | boolean \| string \| undefined | Indicates the currently selected option or tab. |
| role | string | Defines the semantic role exposed to assistive technology. |

#### `indicator`

Step marker that communicates index, active state, or completion.

#### `icon`

Icon rendered inside a completed or custom step indicator.

#### `separator`

Connector line between adjacent steps.

##### Data Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| data-disabled | string \| undefined | Present when the component or item is disabled. |
| data-state | string \| undefined | Stores the component state used by styling hooks. |

#### `wrapper`

Inner layout wrapper for a single step trigger.

#### `title`

Primary title text for a step.

#### `description`

Supporting description for a step.

#### `content`

Panel rendered for the active step content.

##### Data Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| data-selected | string \| undefined | Present when the item is selected. |

##### ARIA Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| aria-labelledby | boolean \| string \| undefined | References the element that labels the control or region. |
| role | string | Defines the semantic role exposed to assistive technology. |

### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| activationMode | "manual" \| "automatic" \| undefined | automatic | Whether keyboard activation happens immediately or only after confirmation. |
| class | ClassValue | — | Class applied to the component root or trigger element. |
| classes | StepperT.Classes \| undefined | — | — |
| clickable | boolean \| undefined | false | Whether steps are clickable for navigation. |
| defaultValue | StepperT.Value \| undefined | — | Default active step value for uncontrolled usage. |
| disabled | boolean \| undefined | false | Whether the entire stepper is disabled. |
| id | string \| undefined | — | Unique identifier for the stepper root element. |
| items | StepperT.Item[] \| undefined | — | Array of steps to display. |
| linear | boolean \| undefined | true | Whether to enforce linear navigation (must complete steps in order). |
| onChange | ((value: StepperT.Value) => void) \| undefined | — | Callback when the active step changes. |
| orientation | "horizontal" \| "vertical" \| undefined | horizontal | The orientation of the stepper. |
| ref | JSX.HTMLElementTags["div"] extends { ref?: infer Ref; } ? Ref : never \| undefined | — | — |
| size | "xs" \| "sm" \| "md" \| "lg" \| "xl" \| undefined | — | — |
| style | JSX.CSSProperties \| undefined | — | — |
| styles | StepperT.Styles \| undefined | — | — |
| value | StepperT.Value \| undefined | — | Controlled active step value. |

### Items

An individual step in the stepper.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| class | string \| undefined | — | Additional class name for the step item. |
| content | JSX.Element | — | Content to display when the step is active. |
| description | JSX.Element | — | Secondary description of the step. |
| disabled | boolean \| undefined | false | Whether the step is disabled. |
| icon | IconT.Name | index + 1 | Icon to display in the step indicator. |
| title | JSX.Element | — | Title of the step. |
| value | StepperT.Value \| undefined | index of the item | Unique value for the step. |

### ARIA

Accessibility attributes and roles emitted by the component markup.

| Attribute | Type | Description |
| --- | --- | --- |
| aria-controls | boolean \| string \| undefined | References the controlled element while the related content is mounted. |
| aria-describedby | boolean \| string \| undefined | References descriptive text associated with the control. |
| aria-hidden | boolean \| string \| undefined | Hides decorative content from assistive technology. |
| aria-labelledby | boolean \| string \| undefined | References the element that labels the control or region. |
| aria-orientation | boolean \| string \| undefined | Communicates horizontal or vertical orientation. |
| aria-selected | boolean \| string \| undefined | Indicates the currently selected option or tab. |
| role | string | Defines the semantic role exposed to assistive technology. |

### Data Attributes

State and slot attributes exposed for styling hooks and selectors.

| Attribute | Type | Description |
| --- | --- | --- |
| data-clickable | string \| undefined | State or slot attribute exposed for styling hooks and selectors. |
| data-disabled | string \| undefined | Present when the component or item is disabled. |
| data-selected | string \| undefined | Present when the item is selected. |
| data-slot | string | Identifies the rendered slot for styling hooks and selectors. |
| data-state | string \| undefined | Stores the component state used by styling hooks. |
