Stepper
Display content divided into a steps sequence.
Dependencies
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.