> ## 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 – Design

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

export const LiveCode = ({children, customHeight, clickToLoad, example, fullWidth, fullHeight, hideCodeInLiveCode, screenshot, screenshotOnly, showCode: showCodeProp}) => {
  const SCREENSHOTS_BASE = "https://servicetitan.github.io/anvil2-docs-live-code/screenshots";
  const STACKBLITZ_BASE = "https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples";
  const [showCodeBlock, setShowCodeBlock] = useState(showCodeProp ?? false);
  const [isLocalOverride, setIsLocalOverride] = useState(false);
  useEffect(() => {
    const examplePath = `/images/live-code-screenshots-tmp/${example}.png`;
    fetch(examplePath, {
      method: "HEAD"
    }).then(r => {
      if (r.ok) setIsLocalOverride(true);
    }).catch(() => {});
  }, [example]);
  const screenshotBase = isLocalOverride ? "/images/live-code-screenshots-tmp" : SCREENSHOTS_BASE;
  if (screenshotOnly) {
    return <Frame className="flex flex-col">
        <div className="flex dark:hidden" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined,
      background: "#FFFFFF"
    }}>
          <img srcset={`${screenshotBase}/${example}.png, ${screenshotBase}/${example}-2x.png 2x`} src={`${screenshotBase}/${example}.png`} alt={example} noZoom />
        </div>
        <div className="hidden dark:flex" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined,
      background: "#141414"
    }}>
          <img srcset={`${screenshotBase}/${example}-dark.png, ${screenshotBase}/${example}-dark-2x.png 2x`} src={`${screenshotBase}/${example}-dark.png`} alt={example} noZoom />
        </div>
      </Frame>;
  }
  if (screenshot) {
    return <Frame className="flex flex-col -mb-2">
        <div className="flex dark:hidden bg-white dark:bg-codeblock border border-gray-950/10 dark:border-white/10 dark:twoslash-dark rounded-2xl overflow-hidden" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined
    }}>
          <img srcset={`${screenshotBase}/${example}.png, ${screenshotBase}/${example}-2x.png 2x`} src={`${screenshotBase}/${example}.png`} alt={example} noZoom />
        </div>

        <div className="hidden dark:flex bg-white dark:bg-codeblock border border-gray-950/10 dark:border-white/10 dark:twoslash-dark rounded-2xl overflow-hidden" style={{
      background: "#141414",
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined
    }}>
          <img srcset={`${screenshotBase}/${example}-dark.png, ${screenshotBase}/${example}-dark-2x.png 2x`} src={`${screenshotBase}/${example}-dark.png`} alt={example} noZoom />
        </div>

        <div className="flex justify-end items-center text-xs py-2 px-1 gap-4">
          {!showCodeProp ? <button className="inline-flex justify-end items-center text-gray-700 dark:text-gray-50 hover:text-blue-500 dark:hover:text-blue-300 transition-colors group self-end gap-1 cursor-pointer" onClick={() => setShowCodeBlock(!showCodeBlock)} style={{
      appearance: "none"
    }}>
              <Icon icon="code" size="12px" className="group-hover:bg-blue-500 dark:group-hover:bg-blue-300" />
              <span>{showCodeBlock ? "Hide code" : "Show code"}</span>
            </button> : null}

          <a className="inline-flex justify-end items-center hover:text-blue-500 dark:hover:text-blue-300 transition-colors group self-end gap-1" href={`${STACKBLITZ_BASE}/${example}?file=src/App.tsx`} target="_blank" rel="noreferrer">
            <Icon icon="bolt" size="12px" className="group-hover:bg-blue-500 dark:group-hover:bg-blue-300" />
            <span>StackBlitz demo</span>
          </a>
        </div>

        <div className="grid transition-[grid-template-rows] duration-300 ease-in-out overflow-auto overflow-y-hidden overflow-x-auto" style={showCodeBlock ? {
      gridTemplateRows: "1fr"
    } : {
      gridTemplateRows: "0fr"
    }}>
          <div style={{
      minHeight: 0,
      overflowX: "auto",
      overflowY: "hidden",
      marginBlockStart: "-1.25rem",
      marginBlockEnd: "-1.5rem"
    }}>
            {children}
          </div>
        </div>
      </Frame>;
  } else {
    return <div style={{
      display: "flex",
      width: fullWidth ? "100%" : "50%",
      minHeight: customHeight ? customHeight : "316px",
      resize: "vertical",
      overflow: "auto"
    }}>
        <iframe title={example} style={{
      flex: 1,
      width: fullWidth ? "100%" : "50%",
      minHeight: customHeight ? customHeight : "316px"
    }} src={`${STACKBLITZ_BASE}/${example}?embed=1&hideNavigation=1&hideExplorer=1&terminalHeight=0&file=src/App.tsx${clickToLoad ? "&ctl=1" : ""}${hideCodeInLiveCode ? "&view=preview" : ""}`} allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" />
      </div>;
  }
};

export const CodePreviewPlaceholder = ({double, fullWidth}) => {
  const single = <div style={{
    width: fullWidth ? "100%" : "50%",
    borderRadius: "1rem",
    display: "flex",
    padding: "1rem",
    flexDirection: "column",
    gap: "0.5rem",
    height: "10rem",
    marginBlockEnd: "1rem"
  }} className="border-width-default border-color-subdued">
      <div className="bg-strong border-radius-large" style={{
    width: "100%",
    flexGrow: "1"
  }} />
      <div className="bg-strong border-radius-large" style={{
    width: "100%",
    flexGrow: "1"
  }} />
    </div>;
  return double ? <div style={{
    display: "flex",
    gap: "1rem"
  }}>
      {single}
      {single}
    </div> : single;
};

<Frame>
  <div className="w-full h-full bg-[#FFFFFF] p-2 rounded flex items-center justify-center">
    <img noZoom src="https://mintcdn.com/servicetitan/H5FwKyiqhPVZ1UJQ/images/docs/web/components/shared/stepper-main-image.png?fit=max&auto=format&n=H5FwKyiqhPVZ1UJQ&q=85&s=d84608ad26e5f8721adf9fc916ef89f5" width="2560" height="62" data-path="images/docs/web/components/shared/stepper-main-image.png" />
  </div>
</Frame>

## Anatomy

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

<Frame>
  <div className="w-full h-full bg-[#FFFFFF] p-2 rounded flex items-center justify-center">
    <img
      src="https://mintcdn.com/servicetitan/usDdQzJfkl6jYYLR/images/docs/web/components/stepper/design/stepper-anatomy.png?fit=max&auto=format&n=usDdQzJfkl6jYYLR&q=85&s=50d62fe3e227ed6af7c756db0f856eb4"
      alt="Stepper
Anatomy"
      width="1968"
      height="294"
      data-path="images/docs/web/components/stepper/design/stepper-anatomy.png"
    />
  </div>
</Frame>

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

<LiveCode example="stepper-horizontal" screenshot fullWidth>
  ```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;
  ```
</LiveCode>

Steppers are horizontal by default.

#### Step Count

<LiveCode example="stepper-children" screenshot fullWidth>
  ```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;
  ```
</LiveCode>

Steppers support 2–6 steps.

## Behavior

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

### Visual States

#### Default

<LiveCode example="stepper-nonlinear-false" screenshot fullWidth>
  ```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;
  ```
</LiveCode>

#### Overflow

Labels wrap when they exceed available space.

<LiveCode example="stepper-overflow" screenshot fullWidth>
  ```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;
  ```
</LiveCode>

#### Responsive

<LiveCode example="stepper-width" screenshot fullWidth>
  ```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;
  ```
</LiveCode>

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.

### When not to use

* For navigation, use a navigation-focused component instead of a Stepper
* Non-linear progression, i.e., steps that can be completed in any order

### Alternatives

#### 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.

<LiveCode example="stepper-active-do" screenshot fullWidth>
  ```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;
  ```
</LiveCode>

<Check>**Do**</Check>

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.

<LiveCode example="stepper-button-do" screenshot fullWidth>
  ```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;
  ```
</LiveCode>

<Check>**Do**</Check>

#### 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.

<LiveCode example="stepper-children-dont" screenshot fullWidth>
  ```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;
  ```
</LiveCode>

<Danger>**Don't**</Danger>

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            |
