Custom Shadcn Stepper for React and Tailwind CSS. A step-by-step process for users to navigate through a series of steps.
import {
Stepper,
StepperContent,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperSeparator,
StepperTrigger,
} from "@/components/byosui/stepper"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.
<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/byosui/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/byosui/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/byosui/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 {
Stepper,
StepperContent,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperSeparator,
StepperTitle,
StepperTrigger,
} from "@/components/byosui/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/byosui/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/byosui/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,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperSeparator,
StepperTitle,
StepperTrigger,
} from "@/components/byosui/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,
StepperDescription,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperSeparator,
StepperTitle,
StepperTrigger,
} from "@/components/byosui/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,
StepperTitle,
StepperTrigger,
} from "@/components/byosui/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 { useState } from "react"
import { Badge } from "@/components/byosui/badge"
import {
Stepper,
StepperContent,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperSeparator,
StepperTitle,
StepperTrigger,
} from "@/components/byosui/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>
)
}
"use client"
import {
Stepper,
StepperContent,
StepperDescription,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperSeparator,
StepperTitle,
StepperTrigger,
} from "@/components/byosui/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 {
Stepper,
StepperContent,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperSeparator,
StepperTrigger,
} from "@/components/byosui/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/byosui/stepper"
import { cn } from "cn"
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 { useState } from "react"
import {
Stepper,
StepperContent,
StepperIndicator,
StepperItem,
StepperNav,
StepperPanel,
StepperSeparator,
StepperTrigger,
} from "@/components/byosui/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>
)
}