Stepper

Import
import { Stepper } from "reshaped";
import type { StepperProps } from "reshaped";
Related components

Stepper is a compound component that handles Stepper.Item components passed as its children. By default, it displays all steps in a horizontal layout. Each item supports rendering title and subtitle, which can be strings or React nodes.

<Stepper>
  <Stepper.Item title="Step 1" subtitle="Step 1 subtitle" />
  <Stepper.Item title="Step 2" />
  <Stepper.Item title="Step 3" />
</Stepper>

You can change the direction of the Stepper to column to display it vertically.

<Stepper direction="column">
  <Stepper.Item title="Step 1" subtitle="Step 1 subtitle" />
  <Stepper.Item title="Step 2" />
  <Stepper.Item title="Step 3" />
</Stepper>

Each item has a completed flag, which replaces its number with a checkmark.

Additionally, you can control which step is currently active with an activeId prop. By default, it uses the index of the item you want to activate. For example, to make the first item active, you can pass activeId={0}.

If you're rendering items conditionally, you might want to rely on specific ids instead. Instead of using an index, you can pass any string as activeId to the Stepper and an id property to each of the items. Items with matching IDs will be rendered as active.

<Stepper activeId={1}>
  <Stepper.Item title="Step 1" subtitle="Step 1 subtitle" completed />
  <Stepper.Item title="Step 2" />
  <Stepper.Item title="Step 3" />
</Stepper>

With the children support in the column layout, you can handle user navigation through the steps. Here's an example where we have two buttons to navigate back and forward between the steps:

function Demo(props) {
  const [activeId, setActiveId] = React.useState(1);

  const content = (
    <View gap={3}>
      <Placeholder />
      <View direction="row" gap={3}>
        <Button onClick={() => setActiveId((prev) => Math.max(0, prev - 1))}>
          Previous
        </Button>
        <Button onClick={() => setActiveId((prev) => Math.min(2, prev + 1))}>
          Next
        </Button>
      </View>
    </View>
  );

  return (
    <Stepper activeId={activeId} direction="column">
      <Stepper.Item
        completed={activeId > 0}
        title="Step 1"
        subtitle="Step subtitle"
      >
        {content}
      </Stepper.Item>
      <Stepper.Item completed={activeId > 1} title="Step 2">
        {content}
      </Stepper.Item>
      <Stepper.Item completed={activeId > 2} title="Step 3 very long title">
        {content}
      </Stepper.Item>
    </Stepper>
  );
}

Depending on where you render the Stepper and the available space, you might need to hide the labels. Control their rendering with the labelDisplay property, hiding them always or conditionally based on the viewport size. Compose the Stepper with other components to indicate the active step when labels are hidden.

Compare how the following example adjusts its rendered content when switching between small and large viewports:

<View gap={2}>
  <Stepper activeId={1} labelDisplay={{ s: "hidden", m: "inline" }}>
    <Stepper.Item title="Step 1" subtitle="Step 1 subtitle" completed />
    <Stepper.Item title="Step 2" />
    <Stepper.Item title="Step 3" />
  </Stepper>

  <Hidden hide={{ s: false, m: true }}>
    <View direction="row" gap={4} justify="space-between">
      <Text weight="medium">Step 2</Text>
      <Text weight="medium">Step 2 of 3</Text>
    </View>
  </Hidden>
</View>