Custom Shadcn Stepper for React and Tailwind CSS. A step-by-step process for users to navigate through a series of steps.
Browse 15 production-ready Shadcn Stepper components for dashboards, forms, and product UI. These examples follow the Radix UI implementation with accessible primitives from the Radix stack and stay fully compatible with Shadcn Create so radius, color, and typography match your configured theme.
Browse all 15 Shadcn Stepper components for copy-ready layouts, dashboards, and forms built with Tailwind CSS in the ReUI library.
import {
Stepper,
StepperContent,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperSeparator,
StepperTrigger,
} from "@/components/reui/stepper"<Stepper defaultValue={1}>
<StepperNav>
<StepperItem step={1}>
<StepperTrigger>
<StepperIndicator>1</StepperIndicator>
</StepperTrigger>
<StepperSeparator />
</StepperItem>
<StepperItem step={2}>
<StepperTrigger>
<StepperIndicator>2</StepperIndicator>
</StepperTrigger>
</StepperItem>
</StepperNav>
<StepperPanel>
<StepperContent value={1}>Content 1</StepperContent>
<StepperContent value={2}>Content 2</StepperContent>
</StepperPanel>
</Stepper>The root component that manages the active step and configuration.
A container for the stepper items, usually displayed as a progress bar or navigation trail.
Represents a single step in the process.
The interactive element used to navigate between steps.
Displays the status of the step (e.g., number, checkmark, or custom icon).
A visual line between steps.
The label for the step.
Additional supporting text for the step.
A container for step content panels.
The actual content associated with a specific step.
type StepIndicators = {
active?: React.ReactNode
completed?: React.ReactNode
inactive?: React.ReactNode
loading?: React.ReactNode
}"use client"
import {
Stepper,
StepperContent,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperSeparator,
StepperTrigger,
} from "@/components/reui/stepper"
const steps = [1, 2, 3, 4]
export function Pattern() {
return (
<Stepper defaultValue={2} className="w-full max-w-md space-y-8">
<StepperNav>
{steps.map((step) => (
<StepperItem key={step} step={step}>
<StepperTrigger>
<StepperIndicator>{step}</StepperIndicator>
</StepperTrigger>
{steps.length > step && (
<StepperSeparator className="group-data-[state=completed]/step:bg-primary" />
)}
</StepperItem>
))}
</StepperNav>
<StepperPanel className="text-sm">
{steps.map((step) => (
<StepperContent
key={step}
value={step}
className="flex items-center justify-center"
>
Step {step} content
</StepperContent>
))}
</StepperPanel>
</Stepper>
)
}
"use client"
import { useState } from "react"
import { Badge } from "@/components/reui/badge"
import {
Stepper,
StepperContent,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperSeparator,
StepperTitle,
StepperTrigger,
} from "@/components/reui/stepper"
import { Button } from "@/components/ui/button"
import { BookUserIcon, CheckIcon, CreditCardIcon, LoaderCircleIcon, LockIcon } from 'lucide-react'
const steps = [
{
title: "User Details",
icon: (
<BookUserIcon className="size-4" />
),
},
{
title: "Payment Info",
icon: (
<CreditCardIcon className="size-4" />
),
},
{
title: "Auth OTP",
icon: (
<LockIcon className="size-4" />
),
},
]
export function Pattern() {
const [currentStep, setCurrentStep] = useState(2)
return (
<Stepper
value={currentStep}
onValueChange={setCurrentStep}
indicators={{
completed: (
<CheckIcon className="size-3.5" />
),
loading: (
<LoaderCircleIcon className="size-3.5 animate-spin" />
),
}}
className="w-full max-w-xl space-y-8"
>
<StepperNav className="gap-3">
{steps.map((step, index) => (
<StepperItem
key={index}
step={index + 1}
className="relative flex-1 items-start"
>
<StepperTrigger
className="flex grow flex-col items-start justify-center gap-2.5"
asChild
>
<StepperIndicator className="data-[state=inactive]:border-border data-[state=inactive]:text-muted-foreground data-[state=completed]:bg-success size-8 border-2 data-[state=completed]:text-white data-[state=inactive]:bg-transparent">
{step.icon}
</StepperIndicator>
<div className="flex flex-col items-start gap-1">
<div className="text-muted-foreground text-[10px] font-semibold uppercase">
Step {index + 1}
</div>
<StepperTitle className="group-data-[state=inactive]/step:text-muted-foreground text-start text-base font-semibold">
{step.title}
</StepperTitle>
<div>
<Badge
size="sm"
variant="primary-light"
className="hidden group-data-[state=active]/step:inline-flex"
>
In Progress
</Badge>
<Badge
variant="success-light"
size="sm"
className="hidden group-data-[state=completed]/step:inline-flex"
>
Completed
</Badge>
<Badge
variant="secondary"
size="sm"
className="text-muted-foreground hidden group-data-[state=inactive]/step:inline-flex"
>
Pending
</Badge>
</div>
</div>
</StepperTrigger>
{steps.length > index + 1 && (
<StepperSeparator className="group-data-[state=completed]/step:bg-success absolute inset-x-0 start-9 top-4 m-0 group-data-[orientation=horizontal]/stepper-nav:w-[calc(100%-2rem)] group-data-[orientation=horizontal]/stepper-nav:flex-none" />
)}
</StepperItem>
))}
</StepperNav>
<StepperPanel className="text-sm">
{steps.map((step, index) => (
<StepperContent
key={index}
value={index + 1}
className="flex items-center justify-center"
>
{step.title} content
</StepperContent>
))}
</StepperPanel>
<div className="flex items-center justify-between gap-2.5">
<Button
variant="outline"
onClick={() => setCurrentStep((prev) => prev - 1)}
disabled={currentStep === 1}
>
Previous
</Button>
<Button
variant="outline"
onClick={() => setCurrentStep((prev) => prev + 1)}
disabled={currentStep === steps.length}
>
Next
</Button>
</div>
</Stepper>
)
}