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 use Base UI primitives from @base-ui/react 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 {
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 className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground data-[state=completed]:bg-green-500 data-[state=completed]:text-white data-[state=inactive]:text-gray-500">
{step}
</StepperIndicator>
</StepperTrigger>
{steps.length > step && (
<StepperSeparator className="group-data-[state=completed]/step:bg-green-500" />
)}
</StepperItem>
))}
</StepperNav>
<StepperPanel className="text-sm">
{steps.map((step) => (
<StepperContent
className="flex w-full items-center justify-center"
key={step}
value={step}
>
Step {step} content
</StepperContent>
))}
</StepperPanel>
</Stepper>
)
}
"use client"
import {
Stepper,
StepperContent,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperSeparator,
StepperTrigger,
} from "@/components/reui/stepper"
import { CheckIcon, LoaderCircleIcon } from 'lucide-react'
const steps = [1, 2, 3]
export function Pattern() {
return (
<Stepper
className="w-full max-w-md"
defaultValue={2}
indicators={{
completed: (
<CheckIcon className="size-3.5" />
),
loading: (
<LoaderCircleIcon className="size-3.5 animate-spin" />
),
}}
>
<StepperNav className="mb-5">
{steps.map((step) => (
<StepperItem key={step} step={step} loading={step === 2}>
<StepperTrigger>
<StepperIndicator className="data-[state=active]:text-primary-foreground data-[state=active]:bg-primary data-[state=active]:border-primary data-[state=inactive]:border-muted size-5 border-2 data-[state=completed]:border-green-500 data-[state=completed]:bg-green-500 data-[state=completed]:text-white">
<span className="bg-primary-foreground hidden size-1.5 rounded-full group-data-[state=active]/step:block"></span>
</StepperIndicator>
</StepperTrigger>
{steps.length > step && (
<StepperSeparator className="group-data-[state=completed]/step:bg-green-500" />
)}
</StepperItem>
))}
</StepperNav>
<StepperPanel className="text-sm">
{steps.map((step) => (
<StepperContent
className="flex w-full items-center justify-center"
key={step}
value={step}
>
Step {step} content
</StepperContent>
))}
</StepperPanel>
</Stepper>
)
}
"use client"
import { useState } from "react"
import {
Stepper,
StepperContent,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperSeparator,
StepperTrigger,
} from "@/components/reui/stepper"
import { Button } from "@/components/ui/button"
const steps = [1, 2, 3, 4]
export function Pattern() {
const [currentStep, setCurrentStep] = useState(2)
return (
<Stepper
value={currentStep}
onValueChange={setCurrentStep}
className="w-full max-w-md space-y-8"
>
<StepperNav>
{steps.map((step) => (
<StepperItem key={step} step={step}>
<StepperTrigger asChild>
<StepperIndicator className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground data-[state=completed]:bg-green-500 data-[state=completed]:text-white data-[state=inactive]:text-gray-500">
{step}
</StepperIndicator>
</StepperTrigger>
{steps.length > step && (
<StepperSeparator className="group-data-[state=completed]/step:bg-green-500" />
)}
</StepperItem>
))}
</StepperNav>
<StepperPanel className="text-sm">
{steps.map((step) => (
<StepperContent
className="flex w-full items-center justify-center"
key={step}
value={step}
>
Step {step} content
</StepperContent>
))}
</StepperPanel>
{/* Buttons */}
<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>
)
}
"use client"
import {
Stepper,
StepperContent,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperSeparator,
StepperTitle,
StepperTrigger,
} from "@/components/reui/stepper"
import { CheckIcon, LoaderCircleIcon } from 'lucide-react'
const steps = [{ title: "Step 1" }, { title: "Step 2" }, { title: "Step 3" }]
export function Pattern() {
return (
<Stepper
className="w-full max-w-md space-y-8"
defaultValue={2}
indicators={{
completed: (
<CheckIcon className="size-3.5" />
),
loading: (
<LoaderCircleIcon className="size-3.5 animate-spin" />
),
}}
>
<StepperNav>
{steps.map((step, index) => (
<StepperItem
key={index}
step={index + 1}
className="relative flex-1 items-start"
>
<StepperTrigger className="flex flex-col gap-2.5">
<StepperIndicator>{index + 1}</StepperIndicator>
<StepperTitle>{step.title}</StepperTitle>
</StepperTrigger>
{steps.length > index + 1 && (
<StepperSeparator className="group-data-[state=completed]/step:bg-primary absolute inset-x-0 top-3 left-[calc(50%+0.875rem)] m-0 group-data-[orientation=horizontal]/stepper-nav:w-[calc(100%-2rem+0.225rem)] 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 {step.title} content
</StepperContent>
))}
</StepperPanel>
</Stepper>
)
}
"use client"
import {
Stepper,
StepperContent,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperTitle,
StepperTrigger,
} from "@/components/reui/stepper"
import { CheckIcon, LoaderCircleIcon } from 'lucide-react'
const steps = [
{ title: "User Details" },
{ title: "Payment Info" },
{ title: "Auth OTP" },
{ title: "Preview Form" },
]
export function Pattern() {
return (
<Stepper
defaultValue={2}
indicators={{
completed: (
<CheckIcon className="size-3.5" />
),
loading: (
<LoaderCircleIcon className="size-3.5 animate-spin" />
),
}}
className="w-full max-w-lg 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-3">
<StepperIndicator>{index + 1}</StepperIndicator>
<StepperTitle className="group-data-[state=inactive]/step:text-muted-foreground text-start font-semibold">
{step.title}
</StepperTitle>
</StepperTrigger>
</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>
</Stepper>
)
}
"use client"
import {
Stepper,
StepperContent,
StepperDescription,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperSeparator,
StepperTitle,
StepperTrigger,
} from "@/components/reui/stepper"
import { CheckIcon, LoaderCircleIcon } from 'lucide-react'
const steps = [
{ title: "Account", description: "Create your account" },
{ title: "Profile", description: "Set up your profile" },
{ title: "Complete", description: "Review and finish" },
]
export function Pattern() {
return (
<Stepper
defaultValue={2}
indicators={{
completed: (
<CheckIcon className="size-3.5" />
),
loading: (
<LoaderCircleIcon className="size-3.5 animate-spin" />
),
}}
className="w-full max-w-md space-y-8"
>
<StepperNav>
{steps.map((step, index) => (
<StepperItem
key={index}
step={index + 1}
className="relative flex-1 items-start"
>
<StepperTrigger className="flex flex-col gap-2.5">
<StepperIndicator>{index + 1}</StepperIndicator>
<StepperTitle>{step.title}</StepperTitle>
<StepperDescription>{step.description}</StepperDescription>
</StepperTrigger>
{steps.length > index + 1 && (
<StepperSeparator className="group-data-[state=completed]/step:bg-primary absolute inset-x-0 top-2.5 left-[calc(50%+0.875rem)] m-0 group-data-[orientation=horizontal]/stepper-nav:w-[calc(100%-2rem+0.225rem)] 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>
</Stepper>
)
}
"use client"
import {
Stepper,
StepperContent,
StepperDescription,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperSeparator,
StepperTitle,
StepperTrigger,
} from "@/components/reui/stepper"
import { CheckIcon, LoaderCircleIcon } from 'lucide-react'
const steps = [
{ title: "Step 1", description: "Description" },
{ title: "Step 2", description: "Description" },
{ title: "Step 3", description: "Description" },
]
export function Pattern() {
return (
<Stepper
defaultValue={2}
indicators={{
completed: (
<CheckIcon className="size-3.5" />
),
loading: (
<LoaderCircleIcon className="size-3.5 animate-spin" />
),
}}
className="w-full max-w-lg space-y-8"
>
<StepperNav>
{steps.map((step, index) => (
<StepperItem key={index} step={index + 1} className="relative">
<StepperTrigger className="flex justify-start gap-1.5">
<StepperIndicator>{index + 1}</StepperIndicator>
<div className="flex flex-col items-start gap-0.5">
<StepperTitle>{step.title}</StepperTitle>
<StepperDescription>{step.description}</StepperDescription>
</div>
</StepperTrigger>
{steps.length > index + 1 && (
<StepperSeparator className="md:mx-2.5" />
)}
</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>
</Stepper>
)
}
"use client"
import {
Stepper,
StepperContent,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperSeparator,
StepperTitle,
StepperTrigger,
} from "@/components/reui/stepper"
import { CheckIcon, LoaderCircleIcon } from 'lucide-react'
const steps = [{ title: "Account" }, { title: "Profile" }, { title: "Review" }]
export function Pattern() {
return (
<Stepper
defaultValue={2}
indicators={{
completed: (
<CheckIcon className="size-3.5" />
),
loading: (
<LoaderCircleIcon className="size-3.5 animate-spin" />
),
}}
className="w-full max-w-md space-y-8"
>
<StepperNav>
{steps.map((step, index) => (
<StepperItem key={index} step={index + 1} className="relative">
<StepperTrigger className="flex justify-start gap-1.5">
<StepperIndicator>{index + 1}</StepperIndicator>
<StepperTitle>{step.title}</StepperTitle>
</StepperTrigger>
{steps.length > index + 1 && (
<StepperSeparator className="group-data-[state=completed]/step:bg-primary md:mx-2.5" />
)}
</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>
</Stepper>
)
}
"use client"
import {
Stepper,
StepperContent,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperTitle,
StepperTrigger,
} from "@/components/reui/stepper"
const steps = [
{ title: "User Details" },
{ title: "Payment Info" },
{ title: "Auth OTP" },
{ title: "Preview Form" },
]
export function Pattern() {
return (
<Stepper defaultValue={2} className="w-full max-w-lg space-y-8">
<StepperNav className="mb-10 gap-5">
{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-3.5">
<StepperIndicator className="bg-border data-[state=active]:bg-primary data-[state=completed]:bg-primary h-1 w-full rounded-full">
<span className="sr-only">{index + 1}</span>
</StepperIndicator>
<StepperTitle className="group-data-[state=inactive]/step:text-muted-foreground text-start font-semibold">
{step.title}
</StepperTitle>
</StepperTrigger>
</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>
</Stepper>
)
}
"use client"
import {
Stepper,
StepperContent,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperSeparator,
StepperTrigger,
} from "@/components/reui/stepper"
import { CheckIcon, LoaderCircleIcon } from 'lucide-react'
const steps = [1, 2, 3]
export function Pattern() {
return (
<div className="flex items-center justify-center">
<Stepper
className="flex flex-col items-center justify-center gap-10"
defaultValue={2}
orientation="vertical"
indicators={{
completed: (
<CheckIcon className="size-3.5" />
),
loading: (
<LoaderCircleIcon className="size-3.5 animate-spin" />
),
}}
>
<StepperNav>
{steps.map((step) => (
<StepperItem key={step} step={step} loading={step === 2}>
<StepperTrigger>
<StepperIndicator className="data-[state=completed]:bg-success data-[state=completed]:text-white">
{step}
</StepperIndicator>
</StepperTrigger>
{steps.length > step && (
<StepperSeparator className="group-data-[state=completed]/step:bg-success" />
)}
</StepperItem>
))}
</StepperNav>
<StepperPanel className="w-56 text-center text-sm">
{steps.map((step) => (
<StepperContent key={step} value={step}>
Step {step} content
</StepperContent>
))}
</StepperPanel>
</Stepper>
</div>
)
}
"use client"
import { useState } from "react"
import {
Stepper,
StepperContent,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperTrigger,
} from "@/components/reui/stepper"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { ArrowLeftIcon } from 'lucide-react'
const steps = [1, 2, 3, 4]
export function Pattern() {
const [currentStep, setCurrentStep] = useState(1)
return (
<div className="w-full max-w-md">
<Stepper value={currentStep} onValueChange={setCurrentStep}>
<StepperNav>
{steps.map((step) => (
<StepperItem
key={step}
step={step}
className="first:rounded-s-full last:rounded-e-full flex-1 overflow-hidden transition-all duration-300"
>
<StepperTrigger
className="w-full flex-col items-start gap-2"
asChild
>
<StepperIndicator className="bg-border h-2 w-full rounded-none!">
<span className="sr-only">{step}</span>
</StepperIndicator>
</StepperTrigger>
</StepperItem>
))}
</StepperNav>
<div className="flex items-center justify-between gap-2.5 py-1">
<Button
variant="link"
onClick={() => setCurrentStep((prev) => prev - 1)}
className={cn(
"px-0",
currentStep === 1 && "pointer-events-none opacity-0"
)}
>
<ArrowLeftIcon className="size-4" />
Back
</Button>
<div className="text-sm font-medium">
<span className="text-foreground">{currentStep}</span>{" "}
<span className="text-muted-foreground/60">/ {steps.length}</span>
</div>
</div>
<StepperPanel className="py-6 text-sm">
{steps.map((step) => (
<StepperContent
className="flex w-full items-center justify-center"
key={step}
value={step}
>
Step {step} content
</StepperContent>
))}
</StepperPanel>
<div className="flex items-center justify-end gap-2.5">
<Button
variant="outline"
onClick={() => setCurrentStep((prev) => prev + 1)}
disabled={currentStep === steps.length}
>
Next
</Button>
</div>
</Stepper>
</div>
)
}
"use client"
import {
Stepper,
StepperContent,
StepperDescription,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperSeparator,
StepperTitle,
StepperTrigger,
} from "@/components/reui/stepper"
import { CheckIcon, LoaderCircleIcon } from 'lucide-react'
const steps = [
{ title: "Account", description: "Create your account" },
{ title: "Profile", description: "Set up your profile" },
{ title: "Review", description: "Confirm your details" },
]
export function Pattern() {
return (
<div className="flex items-center justify-center">
<Stepper
className="flex flex-col items-center justify-center gap-10"
defaultValue={2}
orientation="vertical"
indicators={{
completed: (
<CheckIcon className="size-3.5" />
),
loading: (
<LoaderCircleIcon className="size-3.5 animate-spin" />
),
}}
>
<StepperNav>
{steps.map((step, index) => (
<StepperItem
key={index}
step={index + 1}
className="relative items-start not-last:flex-1"
>
<StepperTrigger className="items-start gap-2.5 pb-12 last:pb-0">
<StepperIndicator className="data-[state=completed]:bg-success data-[state=completed]:text-white">
{index + 1}
</StepperIndicator>
<div className="mt-0.5 text-left">
<StepperTitle>{step.title}</StepperTitle>
<StepperDescription>{step.description}</StepperDescription>
</div>
</StepperTrigger>
{index < steps.length - 1 && (
<StepperSeparator className="group-data-[state=completed]/step:bg-success absolute inset-y-0 top-7 left-3 -order-1 m-0 -translate-x-1/2 group-data-[orientation=vertical]/stepper-nav:h-[calc(100%-2rem)]" />
)}
</StepperItem>
))}
</StepperNav>
<StepperPanel className="w-56 text-center text-sm">
{steps.map((step, index) => (
<StepperContent key={index} value={index + 1}>
{step.title} content
</StepperContent>
))}
</StepperPanel>
</Stepper>
</div>
)
}
"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>
)
}