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
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
Orientation
We can pass the orientation
prop to the Stepper component to set the orientation as "vertical" or "horizontal".
Description
We can add a description to the array of steps
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
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
Sizes
The Stepper component has 3 sizes: sm
, md
, and lg
which can be set using the size
prop.
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
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
To change the general check and error icons, we can use the checkIcon
and
errorIcon
prop inside the Stepper component
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
The onClickStep
function has as parameters the index of the clicked step and
the setter that allows to change to that step index. The setter is useful when
we want to send an onClick event globally and we don't have access to the
useStepper hook.
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 achieve this, we can simply move our footer below all the steps inside the Stepper component
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.
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
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 layouthorizontal-step-container
: Inner wrapper for each step in horizontal layoutvertical-step
: Outer wrapper for each step in vertical layoutvertical-step-container
: Inner wrapper for each step in vertical layoutvertical-step-content
: Content wrapper for each step in vertical layoutstep-button-container
: Wrapper for the step buttonstep-label-container
: Wrapper for the label and descriptionstep-label
: The label of the stepstep-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 stepdata-invalid
: The step with an errordata-loading
: The step in loading statedata-clickable
: The step that is clickabledata-completed
: The step that is completeddata-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 is8px
.
API
Stepper
Prop | Type | Default |
---|---|---|
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
Prop | Type | Default |
---|---|---|
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:
Prop | Type | Default |
---|---|---|
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 |