Sections
Get Started
Components
- Accordion
- Alert
- Alert Dialog
- Aspect Ratio
- Avatar
- Badge
- Breadcrumb
- Button
- Button Group
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Collapsible
- Combobox
- Command
- Context Menu
- Data Table
- Date Picker
- Dialog
- Drawer
- Dropdown Menu
- Empty
- Field
- Form
- Hover Card
- Input
- Input Group
- Input OTP
- Item
- Kbd
- Label
- Menubar
- Native Select
- Navigation Menu
- Pagination
- Pin Input
- Popover
- Progress
- Radio Group
- Range Calendar
- Resizable
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Spinner
- Stepper
- Switch
- Table
- Tabs
- Tags Input
- Textarea
- Toast
- Toggle
- Toggle Group
- Tooltip
- Typography
Forms
Address
Add your address
Shipping
Set your preferred
Payment
Add any payment
Checkout
Confirm your order
Step 1 of 0
<script setup lang="ts">
import { BookUser, CreditCard, Truck } from 'lucide-vue-next'
import { Stepper, StepperDescription, StepperIndicator, StepperItem, StepperSeparator, StepperTitle, StepperTrigger } from '@/components/ui/stepper'
const steps = [
{
step: 1,
title: 'Address',
description: 'Add your address',
icon: BookUser,
},
{
step: 2,
title: 'Shipping',
description: 'Set your preferred',
icon: Truck,
},
{
step: 3,
title: 'Payment',
description: 'Add any payment',
icon: CreditCard,
},
{
step: 4,
title: 'Checkout',
description: 'Confirm your order',
},
]
</script>
<template>
<Stepper class="flex w-10/12 items-start gap-2">
<StepperItem
v-for="item in steps"
:key="item.step"
:step="item.step"
class="relative flex w-full flex-col items-center justify-center"
>
<StepperTrigger>
<StepperIndicator v-slot="{ step }" class="bg-muted">
<template v-if="item.icon">
<component :is="item.icon" class="w-4 h-4" />
</template>
<span v-else>{{ step }}</span>
</StepperIndicator>
</StepperTrigger>
<StepperSeparator
v-if="item.step !== steps[steps.length - 1]?.step"
class="absolute left-[calc(50%+20px)] right-[calc(-50%+10px)] top-5 block h-0.5 shrink-0 rounded-full bg-muted group-data-[state=completed]:bg-primary"
/>
<div class="flex flex-col items-center">
<StepperTitle>
{{ item.title }}
</StepperTitle>
<StepperDescription>
{{ item.description }}
</StepperDescription>
</div>
</StepperItem>
</Stepper>
</template>Installation
pnpm dlx shadcn-vue@latest add stepper
Usage
<script setup lang="ts">
import {
Stepper,
StepperDescription,
StepperIndicator,
StepperItem,
StepperSeparator,
StepperTitle,
StepperTrigger,
} from '@/components/ui/stepper'
</script>
<template>
<Stepper>
<StepperItem :step="1">
<StepperTrigger>
<StepperIndicator>1</StepperIndicator>
<StepperTitle>Step 1</StepperTitle>
<StepperDescription>This is the first step</StepperDescription>
</StepperTrigger>
<StepperSeparator />
</StepperItem>
<StepperItem :step="2">
<StepperTrigger>
<StepperIndicator>2</StepperIndicator>
<StepperTitle>Step 2</StepperTitle>
<StepperDescription>This is the second step</StepperDescription>
</StepperTrigger>
</StepperItem>
</Stepper>
</template>Examples
Horizontal
Your details
Provide your name and email
Company details
A few details about your company
Invite your team
Start collaborating with your team
Step 1 of 0
<script setup lang="ts">
import { Check, Circle, Dot } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
import { Stepper, StepperDescription, StepperItem, StepperSeparator, StepperTitle, StepperTrigger } from '@/components/ui/stepper'
const steps = [
{
step: 1,
title: 'Your details',
description: 'Provide your name and email',
},
{
step: 2,
title: 'Company details',
description: 'A few details about your company',
},
{
step: 3,
title: 'Invite your team',
description: 'Start collaborating with your team',
},
]
</script>
<template>
<Stepper class="flex w-full items-start gap-2">
<StepperItem
v-for="step in steps"
:key="step.step"
v-slot="{ state }"
class="relative flex w-full flex-col items-center justify-center"
:step="step.step"
>
<StepperSeparator
v-if="step.step !== steps[steps.length - 1]?.step"
class="absolute left-[calc(50%+20px)] right-[calc(-50%+10px)] top-5 block h-0.5 shrink-0 rounded-full bg-muted group-data-[state=completed]:bg-primary"
/>
<StepperTrigger as-child>
<Button
:variant="state === 'completed' || state === 'active' ? 'default' : 'outline'"
size="icon"
class="z-10 rounded-full shrink-0"
:class="[state === 'active' && 'ring-2 ring-ring ring-offset-2 ring-offset-background']"
>
<Check v-if="state === 'completed'" class="size-5" />
<Circle v-if="state === 'active'" />
<Dot v-if="state === 'inactive'" />
</Button>
</StepperTrigger>
<div class="mt-5 flex flex-col items-center text-center">
<StepperTitle
:class="[state === 'active' && 'text-primary']"
class="text-sm font-semibold transition lg:text-base"
>
{{ step.title }}
</StepperTitle>
<StepperDescription
:class="[state === 'active' && 'text-primary']"
class="sr-only text-xs text-muted-foreground transition md:not-sr-only lg:text-sm"
>
{{ step.description }}
</StepperDescription>
</div>
</StepperItem>
</Stepper>
</template>Vertical
Your details
Provide your name and email address. We will use this information to create your account
Company details
A few details about your company will help us personalize your experience
Invite your team
Start collaborating with your team by inviting them to join your account. You can skip this step and invite them later
Step 1 of 0
<script setup lang="ts">
import { Check, Circle, Dot } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
import { Stepper, StepperDescription, StepperItem, StepperSeparator, StepperTitle, StepperTrigger } from '@/components/ui/stepper'
const steps = [
{
step: 1,
title: 'Your details',
description:
'Provide your name and email address. We will use this information to create your account',
},
{
step: 2,
title: 'Company details',
description: 'A few details about your company will help us personalize your experience',
},
{
step: 3,
title: 'Invite your team',
description:
'Start collaborating with your team by inviting them to join your account. You can skip this step and invite them later',
},
]
</script>
<template>
<Stepper orientation="vertical" class="mx-auto flex w-full max-w-md flex-col justify-start gap-10">
<StepperItem
v-for="step in steps"
:key="step.step"
v-slot="{ state }"
class="relative flex w-full items-start gap-6"
:step="step.step"
>
<StepperSeparator
v-if="step.step !== steps[steps.length - 1]?.step"
class="absolute left-[18px] top-[38px] block h-[105%] w-0.5 shrink-0 rounded-full bg-muted group-data-[state=completed]:bg-primary"
/>
<StepperTrigger as-child>
<Button
:variant="state === 'completed' || state === 'active' ? 'default' : 'outline'"
size="icon"
class="z-10 rounded-full shrink-0"
:class="[state === 'active' && 'ring-2 ring-ring ring-offset-2 ring-offset-background']"
>
<Check v-if="state === 'completed'" class="size-5" />
<Circle v-if="state === 'active'" />
<Dot v-if="state === 'inactive'" />
</Button>
</StepperTrigger>
<div class="flex flex-col gap-1">
<StepperTitle
:class="[state === 'active' && 'text-primary']"
class="text-sm font-semibold transition lg:text-base"
>
{{ step.title }}
</StepperTitle>
<StepperDescription
:class="[state === 'active' && 'text-primary']"
class="sr-only text-xs text-muted-foreground transition md:not-sr-only lg:text-sm"
>
{{ step.description }}
</StepperDescription>
</div>
</StepperItem>
</Stepper>
</template>Form
Step 1 of 0
<script setup lang="ts">
import { toTypedSchema } from '@vee-validate/zod'
import { Check, Circle, Dot } from 'lucide-vue-next'
import { h, ref } from 'vue'
import { toast } from 'vue-sonner'
import * as z from 'zod'
import { Button } from '@/components/ui/button'
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { Stepper, StepperDescription, StepperItem, StepperSeparator, StepperTitle, StepperTrigger } from '@/components/ui/stepper'
const formSchema = [
z.object({
fullName: z.string(),
email: z.string().email(),
}),
z.object({
password: z.string().min(2).max(50),
confirmPassword: z.string(),
}).refine(
(values) => {
return values.password === values.confirmPassword
},
{
message: 'Passwords must match!',
path: ['confirmPassword'],
},
),
z.object({
favoriteDrink: z.union([z.literal('coffee'), z.literal('tea'), z.literal('soda')]),
}),
]
const stepIndex = ref(1)
const steps = [
{
step: 1,
title: 'Your details',
description: 'Provide your name and email',
},
{
step: 2,
title: 'Your password',
description: 'Choose a password',
},
{
step: 3,
title: 'Your Favorite Drink',
description: 'Choose a drink',
},
]
function onSubmit(values: any) {
toast('You submitted the following values:', {
description: h('pre', { class: 'mt-2 w-[320px] rounded-md bg-neutral-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
})
}
</script>
<template>
<Form
v-slot="{ meta, values, validate }"
as="" keep-values :validation-schema="toTypedSchema(formSchema[stepIndex - 1])"
>
<Stepper v-slot="{ isNextDisabled, isPrevDisabled, nextStep, prevStep, modelValue }" v-model="stepIndex" class="block w-full">
<form
@submit="(e) => {
e.preventDefault()
validate()
if (stepIndex === steps.length && meta.valid) {
onSubmit(values)
}
}"
>
<div class="flex w-full flex-start gap-2">
<StepperItem
v-for="(step, index) in steps"
:key="step.step"
v-slot="{ state }"
class="relative flex w-full flex-col items-center justify-center"
:step="step.step"
>
<StepperSeparator
v-if="step.step !== steps[steps.length - 1].step"
class="absolute left-[calc(50%+20px)] right-[calc(-50%+10px)] top-5 block h-0.5 shrink-0 rounded-full bg-muted group-data-[state=completed]:bg-primary"
/>
<StepperTrigger as-child>
<Button
:variant="state === 'completed' || state === 'active' ? 'default' : 'outline'"
size="icon"
class="z-10 rounded-full shrink-0"
:class="[state === 'active' && 'ring-2 ring-ring ring-offset-2 ring-offset-background']"
:disabled="state !== 'completed' && (index >= (modelValue || 0) && !meta.valid)"
>
<Check v-if="state === 'completed'" class="size-5" />
<Circle v-if="state === 'active'" />
<Dot v-if="state === 'inactive'" />
</Button>
</StepperTrigger>
<div class="mt-5 flex flex-col items-center text-center">
<StepperTitle
:class="[state === 'active' && 'text-primary']"
class="text-sm font-semibold transition lg:text-base"
>
{{ step.title }}
</StepperTitle>
<StepperDescription
:class="[state === 'active' && 'text-primary']"
class="sr-only text-xs text-muted-foreground transition md:not-sr-only lg:text-sm"
>
{{ step.description }}
</StepperDescription>
</div>
</StepperItem>
</div>
<div class="flex flex-col gap-4 mt-4">
<template v-if="stepIndex === 1">
<FormField v-slot="{ componentField }" name="fullName">
<FormItem>
<FormLabel>Full Name</FormLabel>
<FormControl>
<Input type="text" v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="email">
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email " v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</template>
<template v-if="stepIndex === 2">
<FormField v-slot="{ componentField }" name="password">
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input type="password" v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="confirmPassword">
<FormItem>
<FormLabel>Confirm Password</FormLabel>
<FormControl>
<Input type="password" v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</template>
<template v-if="stepIndex === 3">
<FormField v-slot="{ componentField }" name="favoriteDrink">
<FormItem>
<FormLabel>Drink</FormLabel>
<Select v-bind="componentField">
<FormControl>
<SelectTrigger class="w-full!">
<SelectValue placeholder="Select a drink" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectGroup>
<SelectItem value="coffee">
Coffee
</SelectItem>
<SelectItem value="tea">
Tea
</SelectItem>
<SelectItem value="soda">
Soda
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
</FormField>
</template>
</div>
<div class="flex items-center justify-between mt-4">
<Button :disabled="isPrevDisabled" variant="outline" size="sm" @click="prevStep()">
Back
</Button>
<div class="flex items-center gap-3">
<Button v-if="stepIndex !== 3" :type="meta.valid ? 'button' : 'submit'" :disabled="isNextDisabled" size="sm" @click="meta.valid && nextStep()">
Next
</Button>
<Button
v-if="stepIndex === 3" size="sm" type="submit"
>
Submit
</Button>
</div>
</div>
</form>
</Stepper>
</Form>
</template>