Docs
Stepper

Stepper

The Stepper component divides content into sequential steps, offering users a structured way to navigate through processes or information. It provides visual indicators, such as numbering or symbols, to denote each step, and allows interactive navigation between them. With features like feedback and validation, flexible configuration options, and accessibility considerations, it enhances user experience by simplifying complex tasks into manageable chunks and facilitating intuitive progression through sequential content, commonly used in onboarding processes, multi-step forms, and guided tutorials.

Step 1
Step 2
Step 3

Step 1

Installation

yarn add @camped-ui/stepper

Usage

import {
  Step,
  Stepper,
  useStepper,
  type StepItem,
} from "@camped-ui/stepper"
const steps = [
  { label: "Step 1" },
  { label: "Step 2" },
  { label: "Step 3" },
] satisfies StepItem[]
 
export default function StepperDemo() {
  return (
    <div className="flex w-full flex-col gap-4">
      <Stepper initialStep={0} steps={steps}>
        {steps.map(({ label }, index) => {
          return (
            <Step key={label} label={label}>
              <div className="h-40 flex items-center justify-center my-4 border bg-secondary text-primary rounded-md">
                <h1 className="text-xl">Step {index + 1}</h1>
              </div>
            </Step>
          )
        })}
        <Footer />
      </Stepper>
    </div>
  )
}
 
const Footer = () => {
  const {
    nextStep,
    prevStep,
    resetSteps,
    isDisabledStep,
    hasCompletedAllSteps,
    isLastStep,
    isOptionalStep,
  } = useStepper()
  return (
    <>
      {hasCompletedAllSteps && (
        <div className="h-40 flex items-center justify-center my-4 border bg-secondary text-primary rounded-md">
          <h1 className="text-xl">Woohoo! All steps completed! 🎉</h1>
        </div>
      )}
      <div className="w-full flex justify-end gap-2">
        {hasCompletedAllSteps ? (
          <Button size="sm" onClick={resetSteps}>
            Reset
          </Button>
        ) : (
          <>
            <Button
              disabled={isDisabledStep}
              onClick={prevStep}
              size="sm"
              variant="secondary"
            >
              Prev
            </Button>
            <Button size="sm" onClick={nextStep}>
              {isLastStep ? "Finish" : isOptionalStep ? "Skip" : "Next"}
            </Button>
          </>
        )}
      </div>
    </>
  )
}

Examples

Default

Step 1
Step 2
Step 3

Step 1

Orientation

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

Step 1

Step 1

Step 2
Step 3

Description

We can add a description to the array of steps

Step 1Description 1
Step 2Description 2
Step 3Description 3

Step 1

Optional steps

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

In this example, the second step is optional. You can visualize the change of the label in the Next button

Step 1
Step 2
Step 3

Step 1

Variants

There are 3 design variants for the Stepper component:

  • circle: allows you to display each step in a circular shape. The label and description are positioned horizontally next to the button of each step.
  • circle-alt: same circular design as the circle variant but the label and description are positioned vertically below the button of each step.
  • line: this variant features a line layout with the title and description positioned below the line.
Step 1
Step 2
Step 3

Step 1

Sizes

The Stepper component has 3 sizes: sm, md, and lg which can be set using the size prop.

Step 1
Step 2
Step 3

Step 1

Responsive

By using the orientation prop you are able to switch between horizontal (default) and vertical orientations. By default, when in mobile view the Steps component will switch to vertical orientation. You are also able to customize the breakpoint at which the component switches to vertical orientation by using the mobileBreakpoint prop.

State

Sometimes it is useful to display visual feedback to the user depending on some asynchronous logic. In this case we can use the state prop to display a loading or error indicator with the values of loading | error.

This prop can be used globally within the Stepper component or locally in the Step component affected by this state.

Step 1
Step 2
Step 3

Step 1

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.

Step 1
Step 2
Step 3

Step 1

Clickable steps

If you need the step buttons to be clickable and to be able to set a custom logic for the onClick event, we can use the onClickStep prop in the Stepper component globally or in Step locally.

In this example we have placed a global alert when any step is clicked. Try clicking on any step to see the result.

Step 1
Step 2
Step 3

Step 1

When using the vertical orientation, we may want to have the footer buttons inside each step and not located at the end. To achieve this, we can simply move our footer below all the steps inside the Stepper component

Step 1

Step 1

Step 2
Step 3

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 and the react-hook-form hooks to create a form with zod for validations.

You can also use the component with server actions.

Step 1Description 1
Step 2Description 2

This is your public display name.

Custom styles

In this example we will change the style of the steps and the separator. In addition, we will also change the variables that define the size and gap of the icon for each step.

...
  <Stepper
    initialStep={0}
    steps={steps}
    styles={{
      "step-button-container": cn(
        "text-purple-700 rounded-none",
        "data-[current=true]:border-purple-500 data-[current=true]:bg-purple-50",
        "data-[active=true]:bg-purple-500 data-[active=true]:border-purple-500"
      ),
      "horizontal-step":
        "data-[completed=true]:[&:not(:last-child)]:after:bg-purple-500",
    }}
    variables={{
      "--step-icon-size": "60px",
      "--step-gap": "20px",
    }}
  >
  // Rest of the code
  </Stepper>
...
Step 1
Step 2
Step 3

Step 1

To customize the styles of the Steps component, Stepper provides a list of css classes for each part of the component. You can use these classes to override the default styles. Below is a list of the classes that are available.

  • main-container: The main container of the stepper.
  • horizontal-step: Outer wrapper for each step in horizontal layout
  • horizontal-step-container: Inner wrapper for each step in horizontal layout
  • vertical-step: Outer wrapper for each step in vertical layout
  • vertical-step-container: Inner wrapper for each step in vertical layout
  • vertical-step-content: Content wrapper for each step in vertical layout
  • step-button-container: Wrapper for the step button
  • step-label-container: Wrapper for the label and description
  • step-label: The label of the step
  • step-description: The description of the step

In some cases you may want to customize the styles of a step based on its state. For example, you may want to change the color of a step when it is active. To do this, you can use the data attributes defined below.

  • data-active: The active step
  • data-invalid: The step with an error
  • data-loading: The step in loading state
  • data-clickable: The step that is clickable
  • data-completed: The step that is completed
  • data-optional: The step that is optional

Finally, we also have the variables prop that allows you to set values for the css variables that calculate the position of the separator lines. These variables can be useful when we need to set custom elements that have a different size than those offered by the component.

  • --step-icon-size: defines the width of the step icon. It is important to define this value in pixels.
  • --step-gap: defines the gap between the separator and the elements that follow it. The default value is 8px.

API

Stepper

PropTypeDefault
initialStep*
number
steps*
StepItem[]
orientation
"horizontal" | "vertical"
horizontal
size
"sm" | "md" | "lg"
md
state
"loading" | "error"
icon
LucideIcon | React.ComponentType<any>
checkIcon
LucideIcon | React.ComponentType<any>
errorIcon
LucideIcon | React.ComponentType<any>
responsive
boolean
true
mobileBreakpoint
number
768px
scrollTracking
boolean
false
styles
{ [key: string]: string }
variables
{ [key: string]: string }
onClickStep
(index: number, setStep: (index: number) => void) => void
variant
"circle" | "circle-alt" | "line"
circle
expandVerticalSteps
boolean
false

Step

PropTypeDefault
id
string
label
string
description
string
optional
boolean
icon
LucideIcon | React.ComponentType<any>
state
"loading" | "error"
isCompletedStep
boolean
isKeepError
boolean
checkIcon
LucideIcon | React.ComponentType<any>
errorIcon
LucideIcon | React.ComponentType<any>
onClickStep
(index: number, setStep: (index: number) => void) => void

useStepper

In order to use the hook, we simply have to import it and use it inside the <Stepper /> component as a wrapper.

import { useStepper } from "@camped-ui/stepper"
 
export funcion UseStepperDemo() {
  { ... } = useStepper();
 
  return (
    <div>
      { ... }
    </div>
  )
}

The values returned by the hook are the following:

PropTypeDefault
activeStep
number
isLastStep
boolean
isOptionalStep
boolean
isDisabledStep
boolean
isError
boolean
isLoading
boolean
isVertical
boolean
expandVerticalSteps
boolean
nextStep
() => void
prevStep
() => void
setStep
(index: number) => void
resetSteps
() => void
stepCount
number
initialStep
number
clickable
boolean
hasCompletedAllSteps
boolean
currentStep
StepItem