> ## 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 Base – Code

> Stepper Base shows progress through a sequence when the product owns the current step and each step's outcome.

**Implementation**

stepper-base-playground

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

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

function App() {
  return (
    <div style={{ minWidth: "55rem" }}>
      <StepperBase index={2}>
        <StepperBase.List>
          <StepperBase.Step state="complete">Approved</StepperBase.Step>
          <StepperBase.Step state="complete">Reconciled</StepperBase.Step>
          <StepperBase.Step state="complete">
            Approved & Reconciled
          </StepperBase.Step>
          <StepperBase.Step>Scheduled</StepperBase.Step>
        </StepperBase.List>
      </StepperBase>
    </div>
  );
}

export default App;
```

The `StepperBase` provides a controllable stepper primitive. The caller sets the current step and each step's outcome. Use [`Stepper`](https://anvil.servicetitan.com/docs/components/stepper/web) for the linear wizard.

`StepperBase` uses the same visual tokens as [`Stepper`](https://anvil.servicetitan.com/docs/components/stepper/tokens).

## Common Examples

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

function ExampleComponent() {
  return (
    <StepperBase index={2}>
      <StepperBase.List>
        <StepperBase.Step state="complete">Approved</StepperBase.Step>
        <StepperBase.Step state="complete">Reconciled</StepperBase.Step>
        <StepperBase.Step state="complete">
          Approved & Reconciled
        </StepperBase.Step>
        <StepperBase.Step>Scheduled</StepperBase.Step>
      </StepperBase.List>
    </StepperBase>
  );
}
```

### Current and complete together

Completeness is the step `state`, not "this index is behind current." A step can be current and `complete` at the same time.

stepper-base-playground

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

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

function App() {
  return (
    <div style={{ minWidth: "55rem" }}>
      <StepperBase index={2}>
        <StepperBase.List>
          <StepperBase.Step state="complete">Approved</StepperBase.Step>
          <StepperBase.Step state="complete">Reconciled</StepperBase.Step>
          <StepperBase.Step state="complete">
            Approved & Reconciled
          </StepperBase.Step>
          <StepperBase.Step>Scheduled</StepperBase.Step>
        </StepperBase.List>
      </StepperBase>
    </div>
  );
}

export default App;
```

### Step outcomes

Set `state` on each `StepperBase.Step` to `"not started"`, `"started"`, or `"complete"`. The default is `"not started"`. Outcome does not change when `index` changes.

stepper-base-states

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

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

function App() {
  return (
    <Flex direction="column" gap="6" style={{ minWidth: "55rem" }}>
      <StepperBase index={0}>
        <StepperBase.List>
          <StepperBase.Step>Not started</StepperBase.Step>
          <StepperBase.Step>Not started</StepperBase.Step>
          <StepperBase.Step>Not started</StepperBase.Step>
        </StepperBase.List>
      </StepperBase>
      <StepperBase index={1}>
        <StepperBase.List>
          <StepperBase.Step state="complete">Complete</StepperBase.Step>
          <StepperBase.Step state="started">Started</StepperBase.Step>
          <StepperBase.Step>Not started</StepperBase.Step>
        </StepperBase.List>
      </StepperBase>
      <StepperBase index={1}>
        <StepperBase.List>
          <StepperBase.Step state="complete">Complete</StepperBase.Step>
          <StepperBase.Step state="complete">Complete</StepperBase.Step>
          <StepperBase.Step>Not started</StepperBase.Step>
        </StepperBase.List>
      </StepperBase>
    </Flex>
  );
}

export default App;
```

### Adding panel content

Content can be added with `StepperBase.Panel`. The `step` prop on a panel must match the `id` on its `StepperBase.Step`. This is the inverse of `Stepper`, where the step's `controls` prop names the panel.

A step `id` is required only when a panel points at that step. Panel `id` is optional and is generated for `aria-controls` when omitted.

stepper-base-panel

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

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

function App() {
  return (
    <div style={{ minWidth: "55rem" }}>
      <StepperBase defaultIndex={0}>
        <StepperBase.List>
          <StepperBase.Step id="account" state="started">
            Account
          </StepperBase.Step>
          <StepperBase.Step id="profile">Profile</StepperBase.Step>
        </StepperBase.List>
        <StepperBase.Panel step="account">
          <Text>Account details</Text>
        </StepperBase.Panel>
        <StepperBase.Panel step="profile">
          <Text>Profile details</Text>
        </StepperBase.Panel>
      </StepperBase>
    </div>
  );
}

export default App;
```

### Interactive navigation

The track is display-only by default. Set `interactive` to let click and keyboard change the current step. Pair a controlled `index` with `onIndexChange`. `disabled` on a step blocks activation from the track. Step `onClick` notifies only and does not replace `onIndexChange`.

stepper-base-interactive

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

```tsx lines expandable theme={null}
import { useState } from "react";
import { StepperBase } from "@servicetitan/anvil2";

function App() {
  const [index, setIndex] = useState(0);

  return (
    <div style={{ minWidth: "55rem" }}>
      <StepperBase interactive index={index} onIndexChange={setIndex}>
        <StepperBase.List>
          <StepperBase.Step id="one" state={index > 0 ? "complete" : "started"}>
            Account
          </StepperBase.Step>
          <StepperBase.Step
            id="two"
            state={index === 1 ? "started" : "not started"}
          >
            Profile
          </StepperBase.Step>
          <StepperBase.Step id="three" disabled>
            Review
          </StepperBase.Step>
        </StepperBase.List>
        <StepperBase.Panel step="one">Account details</StepperBase.Panel>
        <StepperBase.Panel step="two">Profile details</StepperBase.Panel>
        <StepperBase.Panel step="three">Review details</StepperBase.Panel>
      </StepperBase>
    </div>
  );
}

export default App;
```

> [!NOTE]
>
> Do not mix `Stepper` and `StepperBase` children in the same tree.

## React Accessibility

* The list uses `role="tablist"`. Each step is a `role="tab"`. A visible panel uses `role="tabpanel"`
* The current step has `aria-selected="true"`
* When a panel is present, the step sets `aria-controls` to the panel id
* Disabled steps use `aria-disabled` so `onClick` still fires
* Steps are `tabIndex={-1}` unless `interactive` is true, the step is current, and the step is not disabled
* When `interactive` is true, Arrow Left and Arrow Right move among steps that are not disabled. Enter and Space activate the focused step

**StepperBase Props**

```tsx theme={null}
<StepperBase
  defaultIndex={0}
  index={2}
  interactive={false}
  onIndexChange={(index) => console.log(index)}
>
  <StepperBase.List>...</StepperBase.List>
  <StepperBase.Panel step="approved">...</StepperBase.Panel>
</StepperBase>
```

## `StepperBase` Props

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

**defaultIndex** `number` (default: 0)

Uncontrolled start. Ignored when `index` is set.

**index** `number`

Controlled current step (0-based). Pair with `onIndexChange`.

**interactive** `boolean` (default: false)

When true, click and keyboard can change the current step.

**onIndexChange** `(index: number) =\> void`

Fired when a step is activated.

**StepperBase.List Props**

```tsx theme={null}
<StepperBase.List>
  <StepperBase.Step>Approved</StepperBase.Step>
</StepperBase.List>
```

## `StepperBase.List` Props

The `StepperBase.List` component can accept any valid HTML `div` props. Each `StepperBase.Step` that renders inside the list receives an index in render order, including steps inside fragments or wrappers.

**StepperBase.Step Props**

```tsx theme={null}
<StepperBase.Step
  disabled={false}
  id="approved"
  state="complete"
  onClick={(e, index) => console.log(index)}
>
  Approved
</StepperBase.Step>
```

## `StepperBase.Step` Props

In addition to the props listed below, the `StepperBase.Step` component can accept any valid HTML `button` props except `onClick` and `disabled`, which are replaced by the props below.

**disabled** `boolean` (default: false)

When true, this step cannot become current via click or keyboard.

**id** `string`

Stable id. Required when a `StepperBase.Panel` points at this step.

**onClick** `(e: MouseEvent\<HTMLButtonElement\>, index?: number) =\> void`

Notify only. Does not replace `onIndexChange`.

**state** `"not started" | "started" | "complete"` (default: not started)

Outcome of this step. Independent of whether it is current (`index`).
Current plus any state is valid.

**StepperBase.Panel Props**

```tsx theme={null}
<StepperBase.Panel step="approved">Panel content</StepperBase.Panel>
```

## `StepperBase.Panel` Props

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

**step** `string` *required*

`id` of the controlling `StepperBase.Step`.

**id** `string`

Panel element id. Generated when omitted. Used for `aria-controls`.


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