Documentation
Stepper

Stepper

Display content divided into a steps sequence.

Dependencies

  • Button - Used to render the steps.
  • Separator - Used to render the separator between the steps.

Usage

import {
  Stepper,
  StepperItem,
  StepperFooter
  useStepper,
} from "@/components/ui/stepper"
const steps = [{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }]
 
export default function StepperDemo() {
  return (
    <div className="flex w-full flex-col gap-4">
      <Stepper initialStep={0} steps={steps} orientation="horizontal">
        {steps.map((step, index) => {
          return (
            <StepperItem key={index}>
              <div className="h-40 w-full rounded-lg bg-slate-100 p-4 text-slate-900 dark:bg-slate-300">
                <p>Step {index + 1} content</p>
              </div>
            </StepperItem>
          )
        })}
        <StepperFooter>
          <MyStepperFooter />
        </StepperFooter>
      </Stepper>
    </div>
  )
}
 
function MyStepperFooter() {
  const {
    activeStep,
    isLastStep,
    isOptionalStep,
    isDisabledStep,
    nextStep,
    prevStep,
    resetSteps,
    steps,
  } = useStepper()
 
  return (
    <div className="flex items-center justify-end gap-2">
      {activeStep === steps.length ? (
        <>
          <Button onClick={resetSteps}>Reset</Button>
        </>
      ) : (
        <>
          <Button disabled={isDisabledStep} onClick={prevStep}>
            Prev
          </Button>
          <Button onClick={nextStep}>
            {isLastStep ? "Finish" : isOptionalStep ? "Skip" : "Next"}
          </Button>
        </>
      )}
    </div>
  )
}

API

<Stepper />

interface StepperProps {
  steps: {
    label: string | React.ReactNode
    description?: string | React.ReactNode
    icon?: React.ReactNode
    optional?: boolean
  }[]
  initialStep: number
  orientation?: "vertical" | "horizontal"
  labelOrientation?: "vertical" | "horizontal"
  scrollTracking?: boolean
  variant?: VariantProps<typeof buttonVariants>["variant"]
  status?: "default" | "success" | "error" | "loading"
  isClickable?: boolean
}

<StepperItem />

interface StepperItemProps {
  onStepperItemClick?: () => void
}

useStepper

useStepper returns values and functions that allow to render the stepper in a modular way.

function useStepper(): StepperProps & {
  nextStep: () => void
  prevStep: () => void
  resetSteps: () => void
  setStep: (step: number) => void
  setStatus: (status: "default" | "success" | "error" | "loading") => void
  activeStep: number
  isDisabledStep: boolean
  isLastStep: boolean
  isOptionalStep: boolean
  isFinished: boolean
}

Default

Orientation

We can pass the orientation prop to the Stepper component to set the orientation as "vertical" or "horizontal".

Descriptions

We can add a description to the array of steps

Label orientation

If you would like the labels to be positioned below the step icons you can do so using the labelOrientation prop on the Stepper component.

Optional steps

If you want to make a step optional, you can add optional: true in the array of steps.

Status

Sometimes it is useful to show visual feedback to the user depending on some asynchronous logic. In this case we can use the status prop on the Stepper component to show a loading indicator, a success indicator or an error indicator.

Custom Icons

If you want to show custom icons instead of the default numerical indicators, you can do so by using the icon prop on the Step component.

Custom onClick item event

If you need to control the onClick event when a user clicks on a step, you can set the onStepperItemClick prop. This event overrides the default setStep, so you must customize it with useStepper.

Responsive

If you want the stepper to be responsive you can make it responsive by using some logic of your own so that the orientation prop changes according to the screen size.

For example, you can use a hook like useMediaQuery that tells you when the screen becomes a certain size in this way:

import { useMediaQuery } from "@/hooks/use-media-query"
import {
  Stepper,
  ...
} from "@/components/ui/stepper"
 
export default function Page() {
  const isMobile = useMediaQuery("(max-width: 640px)")
  return (
    <Stepper orientation={isMobile ? "vertical" : "horizontal"}>
      {/* ... */}
    </Stepper>
  )
}

Footer inside the step

When using the vertical orientation, we may want to have the footer buttons inside each step and not located at the end. To do this, we can change the footer structure and place it below the first internal children of StepperItem.

const steps = [{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }]
 
export default function StepperDemo() {
  return (
    <div className="flex w-full flex-col gap-4">
      <Stepper initialStep={0} steps={steps} orientation="horizontal">
        {steps.map((step, index) => {
          return (
            <StepperItem key={index}>
              <div className="h-40 w-full rounded-lg bg-slate-100 p-4 text-slate-900 dark:bg-slate-300">
                <p>Step {index + 1} content</p>
              </div>
              <StepperFooter>
                <MyStepperFooter />
              </StepperFooter>
            </StepperItem>
          )
        })}
      </Stepper>
    </div>
  )
}
 
function MyStepperFooter() {
  ...
}

Scroll tracking

If you would like the stepper to scroll to the active step when it is not in view you can do so using the scrollTracking prop on the Stepper component.

With Forms

If you want to use the stepper with forms, you can do so by using the useStepper hook to control the component.

This example uses the Form component of shadcn and the react-hook-form hooks to create a form with zod for validations.

You can also use the component with server actions.