> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Stepper - Web

> Steppers show a user's progress through multiple steps.

> [!NOTE]
>
> For development, import `Stepper` from
> `@servicetitan/anvil2`. For design, find it in the
> [Anvil2 file](https://www.figma.com/design/xaNQsvRKczoxu7pj0M6QT8/Anvil2-v3?node-id=2622-7218).

![](https://mintcdn.com/servicetitan/H5FwKyiqhPVZ1UJQ/images/docs/web/components/shared/stepper-main-image.png?fit=max\&auto=format\&n=H5FwKyiqhPVZ1UJQ\&q=85\&s=d84608ad26e5f8721adf9fc916ef89f5)

## Anatomy

The Stepper consists of four primary elements that work together to show a user's progress through multiple steps.

![Stepper
Anatomy](https://mintcdn.com/servicetitan/usDdQzJfkl6jYYLR/images/docs/web/components/stepper/design/stepper-anatomy.png?fit=max\&auto=format\&n=usDdQzJfkl6jYYLR\&q=85\&s=50d62fe3e227ed6af7c756db0f856eb4)

1. Completed step
2. Current step
3. Not started step
4. Step label

## Options

The Stepper supports horizontal orientation and configurable step count to accommodate various progress tracking scenarios.

#### Horizontal

stepper-horizontal

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/stepper-horizontal?file=src/App.tsx)

```tsx lines theme={null}
import { Stepper } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "55rem" }}>
      <Stepper defaultIndex={1}>
        <Stepper.List>
          <Stepper.Step id="t1" controls="p1">
            Step Label
          </Stepper.Step>
          <Stepper.Step id="t2" controls="p2">
            Step Label
          </Stepper.Step>
          <Stepper.Step id="t3" controls="p3">
            Step Label
          </Stepper.Step>
          <Stepper.Step id="t4" controls="p4">
            Step Label
          </Stepper.Step>
        </Stepper.List>
      </Stepper>
    </div>
  );
}

export default App;
```

Steppers are horizontal by default.

#### Step Count

stepper-children

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/stepper-children?file=src/App.tsx)

```tsx lines theme={null}
import { Stepper, Flex } from "@servicetitan/anvil2";

function App() {
  return (
    <Flex direction="column" gap="3" style={{ minWidth: "55rem" }}>
      <Stepper defaultIndex={1}>
        <Stepper.List>
          <Stepper.Step id="t1" controls="p1">
            Step Label
          </Stepper.Step>
          <Stepper.Step id="t2" controls="p2">
            Step Label
          </Stepper.Step>
          <Stepper.Step id="t3" controls="p3">
            Step Label
          </Stepper.Step>
        </Stepper.List>
      </Stepper>
      <Stepper defaultIndex={1}>
        <Stepper.List>
          <Stepper.Step id="t1" controls="p1">
            Step Label
          </Stepper.Step>
          <Stepper.Step id="t2" controls="p2">
            Step Label
          </Stepper.Step>
          <Stepper.Step id="t3" controls="p3">
            Step Label
          </Stepper.Step>
          <Stepper.Step id="t4" controls="p4">
            Step Label
          </Stepper.Step>
          <Stepper.Step id="t5" controls="p5">
            Step Label
          </Stepper.Step>
          <Stepper.Step id="t6" controls="p6">
            Step Label
          </Stepper.Step>
        </Stepper.List>
      </Stepper>
    </Flex>
  );
}

export default App;
```

Steppers support 2–6 steps.

## Behavior

The Stepper responds to different screen sizes with responsive behaviors while handling label overflow.

### Visual States

#### Default

stepper-nonlinear-false

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/stepper-nonlinear-false?file=src/App.tsx)

```tsx lines theme={null}
import { Stepper } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "55rem" }}>
      <Stepper defaultIndex={1}>
        <Stepper.List>
          <Stepper.Step id="t1" controls="p1">
            Default
          </Stepper.Step>
          <Stepper.Step id="t1" controls="p1">
            Selected
          </Stepper.Step>
          <Stepper.Step id="t3" controls="p3">
            In Progress
          </Stepper.Step>
          <Stepper.Step id="t3" controls="p3">
            Complete
          </Stepper.Step>
          <Stepper.Step id="t3" controls="p3" data-interactive="focus-visible">
            Focus Visible (false)
          </Stepper.Step>
        </Stepper.List>
      </Stepper>
    </div>
  );
}

export default App;
```

#### Overflow

Labels wrap when they exceed available space.

stepper-overflow

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/stepper-overflow?file=src/App.tsx)

```tsx lines theme={null}
import { Stepper, Flex } from "@servicetitan/anvil2";

function App() {
  return (
    <Flex direction="column" gap="3" style={{ minWidth: "55rem" }}>
      <Stepper defaultIndex={1}>
        <Stepper.List>
          <Stepper.Step controls="p1">
            Step Label that is very long and will overflow
          </Stepper.Step>
          <Stepper.Step controls="p2">Step Label</Stepper.Step>
          <Stepper.Step controls="p3">Step Label</Stepper.Step>
          <Stepper.Step controls="p4">Step Label</Stepper.Step>
          <Stepper.Step controls="p5">Step Label</Stepper.Step>
          <Stepper.Step controls="p6">Step Label</Stepper.Step>
        </Stepper.List>
      </Stepper>
      Add vertical example here
    </Flex>
  );
}

export default App;
```

#### Responsive

stepper-width

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/stepper-width?file=src/App.tsx)

```tsx lines theme={null}
import { Stepper } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ maxWidth: 360, width: "100%" }}>
      <Stepper defaultIndex={1}>
        <Stepper.List>
          <Stepper.Step id="t1" controls="p1">
            Step Label
          </Stepper.Step>
          <Stepper.Step id="t2" controls="p2">
            Step Label
          </Stepper.Step>
          <Stepper.Step id="t3" controls="p3">
            Step Label
          </Stepper.Step>
          <Stepper.Step id="t4" controls="p4">
            Step Label
          </Stepper.Step>
          <Stepper.Step id="t5" controls="p5">
            Step Label
          </Stepper.Step>
        </Stepper.List>
      </Stepper>
    </div>
  );
}

export default App;
```

Smaller screens use the mobile variant.

## Usage Guidelines

Use the Stepper when users need to be guided through a linear series of steps to complete a task.

| Standard | Requirement |
| - | - |
| DoKeep one active step | The active step is always the step the user is specifically on; see [Always have one Step active in incomplete Steppers](#always-have-one-step-active-in-incomplete-steppers). |
| DoNavigate with Buttons | The Stepper alone should never be the only means of navigating between steps — provide forward and back actions elsewhere; see [Always use Buttons to navigate the Stepper](#always-use-buttons-to-navigate-the-stepper). |
| Don'tNavigation | Use a navigation-focused component instead of a Stepper. |
| Don'tNon-linear progression | Steps that can be completed in any order. See [Stepper Base](https://anvil.servicetitan.com/docs/components/stepper/base/web.md) for an alternative. |
| Don'tToo few or too many steps | Steppers support 2–6 steps; avoid having too few or too many. See [Number of steps](#number-of-steps). |

### Alternatives

#### Stepper vs Stepper Base

Stepper is a linear wizard. Completeness is derived from the current step. Use Next and Previous to move through the flow.

Stepper Base is a status tracker. The product sets the current step and each step's outcome. A step can be current and complete at the same time. See [Stepper Base](https://anvil.servicetitan.com/docs/components/stepper/base/web.md).

#### Stepper vs Edit Card

Steppers move through a linear progression of steps. Edit Cards can be used for both linear and unrelated blocks of content.

Steppers are typically scoped to the whole view, e.g., a fullscreen Dialog, while an Edit Card can be scoped within smaller sections of a view, e.g., part of a Drawer screen.

Steppers can only give summaries of the flow indirectly, typically on the final step of a flow, while an Edit Card can show a summary in most of its states.

Steppers and Edit Cards may be used together if a particular flow is complex enough.

#### Stepper vs Tab

Tabs help organize information about a subject on the page, while a Stepper helps complete a flow. They can't substitute for each other, although both could be used on the same page.

### How to Use

#### Always have one Step active in incomplete Steppers

The active step is always the step the user is specifically on. By default this is the first step.

stepper-active-do

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/stepper-active-do?file=src/App.tsx)

```tsx lines theme={null}
import { Stepper } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "55rem" }}>
      <Stepper defaultIndex={1}>
        <Stepper.List>
          <Stepper.Step id="t1" controls="p1">
            Step 1
          </Stepper.Step>
          <Stepper.Step id="t2" controls="p2">
            Step 2
          </Stepper.Step>
          <Stepper.Step id="t3" controls="p3">
            Step 3
          </Stepper.Step>
        </Stepper.List>
      </Stepper>
    </div>
  );
}

export default App;
```

> [!TIP]
>
> **Do**

Example on step 2. In this scenario, step 1 will always be completed.

#### Always use Buttons to navigate the Stepper

The Stepper alone should never be the only means of navigating between steps. Actions to go forward and back should be provided somewhere in the experience.

stepper-button-do

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/stepper-button-do?file=src/App.tsx)

```tsx lines theme={null}
import { Stepper, Flex } from "@servicetitan/anvil2";

function App() {
  const stepData = [
    {
      id: "s1",
      controls: "p1",
      title: "Step 1",
      content: "Step 1",
    },
    {
      id: "s2",
      controls: "p2",
      title: "Step 2",
      content: "Step 2",
    },
    {
      id: "s3",
      controls: "p3",
      title: "Step 3",
      content: "Step 3",
    },
    {
      id: "s4",
      controls: "p4",
      title: "Step 4",
      content: "Step 4",
    },
  ];

  return (
    <Stepper allowNavigateToPrevStep flexGrow="1">
      <Stepper.List>
        {stepData.map((step) => (
          <Stepper.Step key={step.id} id={step.id} controls={step.controls}>
            {step.title}
          </Stepper.Step>
        ))}
      </Stepper.List>
      {stepData.map((step) => (
        <Stepper.Panel
          id={step.controls}
          key={step.controls}
          style={{ minHeight: "200px" }}
        >
          {step.content}
        </Stepper.Panel>
      ))}
      <Flex gap="3" justifyContent="flex-end">
        <Stepper.PrevButton />
        <Stepper.NextButton />
      </Flex>
    </Stepper>
  );
}

export default App;
```

> [!TIP]
>
> **Do**

#### Linear steps

Steppers should follow a linear sequence, meaning a user must complete the steps in a specific order.

#### Returning to, and editing previous steps

When users return to previous steps in an incomplete flow, the Stepper itself will prevent users from returning back. The component does not alter user responses, the consequences of users editing prior steps is on the implementor. Some options to consider include:

* Clearing all subsequent steps when a user edits a previous step
* Identifying which changes impact downstream inputs, and warning users about the changes. No impact changes do not influence future steps.

#### Number of steps

Steppers support 2–6 steps. If a flow requires more than 6 steps, consider breaking it up into smaller chunks.

stepper-children-dont

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/stepper-children-dont?file=src/App.tsx)

```tsx lines theme={null}
import { Stepper, Flex } from "@servicetitan/anvil2";

function App() {
  return (
    <Flex direction="column" gap="10" style={{ minWidth: "55rem" }}>
      <Stepper>
        <Stepper.List>
          <Stepper.Step id="t0" controls="p0">
            One step Stepper
          </Stepper.Step>
        </Stepper.List>
      </Stepper>
      <Stepper>
        <Stepper.List>
          <Stepper.Step id="t1" controls="p1">
            Step 1
          </Stepper.Step>
          <Stepper.Step id="t2" controls="p2">
            Step 2
          </Stepper.Step>
          <Stepper.Step id="t3" controls="p3">
            Step 3
          </Stepper.Step>
          <Stepper.Step id="t4" controls="p4">
            Step 4
          </Stepper.Step>
          <Stepper.Step id="t5" controls="p5">
            Step 5
          </Stepper.Step>
          <Stepper.Step id="t6" controls="p6">
            Step 6
          </Stepper.Step>
          <Stepper.Step id="t7" controls="p7">
            Step 7
          </Stepper.Step>
        </Stepper.List>
      </Stepper>
    </Flex>
  );
}

export default App;
```

> [!CAUTION]
>
> **Don't**

Avoid having too few or too many steps.

#### Avoid conditionally generated steps

Within an established Stepper, user decisions within the flow should not change the number of steps, i.e., if a flow starts with 5 steps, it should always end with 5 steps.

## Keyboard Interaction

Users can navigate the Stepper using standard keyboard controls.

| Key | Interaction |
| - | - |
| Tab | Navigates linked steps in normal Tab order |
| Enter | Selects the focused linked step |

## Code

**Implementation**

stepper-playground

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/stepper-playground?file=src/App.tsx)

```tsx lines expandable theme={null}
import { Stepper, Flex } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "55rem" }}>
      <Stepper defaultIndex={1}>
        <Stepper.List>
          <Stepper.Step id="t1" controls="p1">
            First Step
          </Stepper.Step>
          <Stepper.Step id="t2" controls="p2">
            Second Step
          </Stepper.Step>
          <Stepper.Step id="t3" controls="p3">
            Third Step
          </Stepper.Step>
        </Stepper.List>
        <Flex gap="3" justifyContent="flex-end">
          <Stepper.PrevButton />
          <Stepper.NextButton />
        </Flex>
      </Stepper>
    </div>
  );
}

export default App;
```

For flows where you need additional control over step progression, use [`StepperBase`](https://anvil.servicetitan.com/docs/components/stepper/base/web).

## Common Examples

```tsx theme={null}
import { Stepper } from "@servicetitan/anvil2";

function ExampleComponent() {
  return (
    <Stepper>
      <Stepper.List>
        <Stepper.Step id="t1" controls="p1">
          First Step
        </Stepper.Step>
        <Stepper.Step id="t2" controls="p2">
          Second Step
        </Stepper.Step>
      </Stepper.List>
      <Stepper.Panel id="p1">{/* panel content */}</Stepper.Panel>
      <Stepper.Panel id="p2">{/* panel content */}</Stepper.Panel>
      <Flex gap="3" justifyContent="flex-end">
        <Stepper.PrevButton />
        <Stepper.NextButton />
      </Flex>
    </Stepper>
  );
}
```

### Adding stepper content

Content can be added to the `Stepper` by using `Stepper.Panel`. The `id` on a `Stepper.Panel` must match with the `controls` on its associated `Stepper.Step` to ensure proper functionality and display of the content.

Content can be added to the end of the `Stepper` by using `Stepper.FinalPanel`.

stepper-panel

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/stepper-panel?file=src/App.tsx)

```tsx lines expandable theme={null}
import { Stepper, Flex } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "55rem" }}>
      <Stepper defaultIndex={1}>
        <Stepper.List>
          <Stepper.Step id="t1" controls="p1">
            Step 1
          </Stepper.Step>
          <Stepper.Step id="t2" controls="p2">
            Step 2
          </Stepper.Step>
          <Stepper.Step id="t3" controls="p3">
            Step 3
          </Stepper.Step>
          <Stepper.Step id="t4" controls="p4">
            Step 4
          </Stepper.Step>
        </Stepper.List>
        <Stepper.Panel id="p1">Step 1 Content</Stepper.Panel>
        <Stepper.Panel id="p2">Step 2 Content</Stepper.Panel>
        <Stepper.Panel id="p3">Step 3 Content</Stepper.Panel>
        <Stepper.FinalPanel>The process is complete.</Stepper.FinalPanel>
        <Flex gap="3" justifyContent="flex-end">
          <Stepper.PrevButton />
          <Stepper.NextButton />
        </Flex>
      </Stepper>
    </div>
  );
}

export default App;
```

### Preventing step on stepper next button

In some cases, validation or other asynchronous functionality needs to occur before the user progresses to the next step.

To prevent the `Stepper.NextButton` from going to the next step on click, return either false or a `Promise` that resolves false to the `onClick` prop.

stepper-nextbutton-onclick

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/stepper-nextbutton-onclick?file=src/App.tsx)

```tsx lines expandable theme={null}
import { Stepper, Checkbox, Flex } from "@servicetitan/anvil2";
import { useState } from "react";

function App() {
  const [firstPanelValid, setFirstPanelValid] = useState(false);
  const [error, setError] = useState<string>();
  const handleNextClick = () => {
    if (firstPanelValid === false) {
      setError("Select this checkbox to continue");
    } else {
      setError(undefined);
    }
    return firstPanelValid;
  };

  return (
    <div style={{ minWidth: "55rem" }}>
      <Stepper>
        <Stepper.List>
          <Stepper.Step id="t1" controls="p1">
            Step 1
          </Stepper.Step>
          <Stepper.Step id="t2" controls="p2">
            Step 2
          </Stepper.Step>
        </Stepper.List>
        <Stepper.Panel id="p1">
          <Checkbox
            onChange={(e) => {
              if (e && e.target.checked) setError(undefined);
              setFirstPanelValid(e?.target.checked ?? false);
            }}
            label="Check this box to continue"
            checked={firstPanelValid}
            error={!!error}
            description={error}
          />
        </Stepper.Panel>
        <Stepper.Panel id="p2">Step 2 Content</Stepper.Panel>
        <Stepper.FinalPanel>The process is complete.</Stepper.FinalPanel>
        <Flex gap="3" justifyContent="flex-end">
          <Stepper.PrevButton />
          <Stepper.NextButton onClick={handleNextClick} />
        </Flex>
      </Stepper>
    </div>
  );
}

export default App;
```

**Stepper Props**

```tsx theme={null}
<Stepper
  allowNavigateToPrevStep={false}
  defaultIndex={0}
  onChange={(data) => console.log(data.currentStepId, data.currentStepIndex)}
  onComplete={() => console.log("Completed")}
>
  <Stepper.List>...</Stepper.List>
  <Stepper.Panel>...</Stepper.Panel>
</Stepper>
```

## `Stepper` Props

In addition to the props listed below, the `Stepper` component can accept any valid HTML `div` props.

**allowNavigateToPrevStep** `boolean`

Allows the user to navigate to previous steps by clicking the step.

**defaultIndex** `number`

The index of the step to display on mount (0-based). To start the Stepper
in the completed state with `Stepper.FinalPanel` visible and all steps
marked complete, set this to the total number of steps (e.g.
`steps.length`).

**onChange** `(data) =\> void`

The `data` object has two values: `currentStepId` and `currentStepIndex`

**onComplete** `() =\> void`

**Stepper.List Props**

```tsx theme={null}
<Stepper.List>
  <Stepper.Step id="t1" controls="p1">
    First Step
  </Stepper.Step>
</Stepper.List>
```

## `Stepper.List` Props

The `Stepper.List` component can accept any valid HTML `div` props.

**Stepper.Panel Props**

```tsx theme={null}
<Stepper.Panel id="p1">Panel content</Stepper.Panel>
```

## `Stepper.Panel` Props

The `Stepper.Panel` component can accept any valid HTML `div` props.

**Stepper.FinalPanel Props**

```tsx theme={null}
<Stepper.FinalPanel>Final panel content</Stepper.FinalPanel>
```

## `Stepper.FinalPanel` Props

The `Stepper.FinalPanel` component can accept any valid HTML `div` props.

**Stepper.PrevButton Props**

```tsx theme={null}
<Stepper.PrevButton appearance="secondary" size="medium" onClick={() => {}} />
```

## `Stepper.PrevButton` Props

The `Stepper.PrevButton` component accepts any props that the [Button](https://anvil.servicetitan.com/docs/components/button/design) component accepts, except for `label`.

**Stepper.NextButton Props**

```tsx theme={null}
<Stepper.NextButton
  appearance="primary"
  size="medium"
  nextLabel="Skip"
  completeLabel="Finish"
  onClick={(e) => {
    // Return false or Promise<false> to prevent advancing
    return false;
  }}
/>
```

## `Stepper.NextButton` Props

The `Stepper.NextButton` component accepts any props that the [Button](https://anvil.servicetitan.com/docs/components/button/design) component accepts, except for `label`, and with an enhanced `onClick` prop:

**completeLabel** `string` (default: Complete)

Custom label for the complete button on the final step.

**nextLabel** `string` (default: Next)

Custom label for the next button.

**onClick** `(e: MouseEvent) =\> void | boolean | Promise\<boolean\>`

If the `onClick` handler returns `false` or a `Promise` that resolves to `false`,
the stepper will not advance.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.