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
- Number Field
- 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
Brief
Choose the product scope
Design
Confirm the visual language
Release
Ship the documented component
Step 1 of 0
<script setup lang="ts">
import {
VStepper,
VStepperDescription,
VStepperIndicator,
VStepperItem,
VStepperSeparator,
VStepperTitle,
VStepperTrigger,
} from '@ui-kit/src/components/Base/VStepper'
const steps = [
{
description: 'Choose the product scope',
step: 1,
title: 'Brief',
},
{
description: 'Confirm the visual language',
step: 2,
title: 'Design',
},
{
description: 'Ship the documented component',
step: 3,
title: 'Release',
},
]
</script>
<template>
<VStepper class="flex w-full max-w-2xl items-start gap-4">
<VStepperItem
v-for="item in steps"
:key="item.step"
:step="item.step"
class="flex w-full flex-col items-center gap-3"
>
<VStepperTrigger>
<VStepperIndicator>
{{ item.step }}
</VStepperIndicator>
</VStepperTrigger>
<VStepperSeparator v-if="item.step !== steps[steps.length - 1]?.step" />
<div class="flex flex-col items-center gap-1 text-center">
<VStepperTitle>
{{ item.title }}
</VStepperTitle>
<VStepperDescription>
{{ item.description }}
</VStepperDescription>
</div>
</VStepperItem>
</VStepper>
</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>