UISteps
UISteps
A versatile steps component that guides users through multi-step processes with support for various layouts, statuses, and customization options.
<template> <UISteps v-model="currentStep" :steps="steps" :direction="'horizontal'" @change="handleStepChange" > <template #step="{ step, index }"> <div class="space-y-1"> <div class="font-medium"> {{ step.title }} </div> <div class="text-sm text-gray-500"> {{ step.description }} </div> </div> </template> </UISteps></template>
<script setup lang="ts">interface Step { title: string description?: string status?: 'wait' | 'process' | 'finish' | 'error' disabled?: boolean icon?: Component}
const currentStep = ref(0)
const steps: Step[] = [ { title: 'Basic Info', description: 'Enter basic information' }, { title: 'Details', description: 'Fill in the details' }, { title: 'Review', description: 'Review and submit' }]
const handleStepChange = (step: number) => { console.log('Step changed:', step)}</script>Props
modelValue(number): Current step indexsteps(array): Steps configurationdirection(‘horizontal’ | ‘vertical’): Steps directionsize(‘sm’ | ‘md’ | ‘lg’): Component sizestatus(string): Overall statusprogressDot(boolean): Use dot styleresponsive(boolean): Enable responsive modelabelPlacement(‘horizontal’ | ‘vertical’): Label placementinitial(number): Initial stepcurrent(number): Current stepprocessStatus(string): Process step statusfinishStatus(string): Finish step statusalignCenter(boolean): Center alignment
Events
update:modelValue: Step changedchange: Step changedfinish: Process finishederror: Error occurredclick: Step clicked
Slots
step: Custom step contenttitle: Custom title contentdescription: Custom description contenticon: Custom icon contentprogress: Custom progress contenttail: Custom tail content
Usage Examples
- Basic Steps:
<template> <UISteps v-model="currentStep" :steps="steps" /></template>
<script setup>const currentStep = ref(0)const steps = [ { title: 'Step 1' }, { title: 'Step 2' }, { title: 'Step 3' }]</script>- Form Wizard:
<template> <div class="space-y-8"> <UISteps v-model="currentStep" :steps="formSteps" :status="formStatus" > <template #step="{ step, index }"> <div class="space-y-1"> <div class="font-medium"> {{ step.title }} </div> <div class="text-sm text-gray-500"> {{ step.description }} </div> </div> </template> </UISteps>
<div class="space-y-6"> <component :is="currentComponent" v-model="formData[currentStep]" @valid="stepValid = $event" />
<div class="flex justify-between"> <UIButton variant="outline" :disabled="currentStep === 0" @click="previousStep" > Previous </UIButton>
<UIButton type="primary" :loading="submitting" :disabled="!stepValid" @click="nextStep" > {{ isLastStep ? 'Submit' : 'Next' }} </UIButton> </div> </div> </div></template>
<script setup>const currentStep = ref(0)const stepValid = ref(false)const submitting = ref(false)const formStatus = ref('process')
const formSteps = [ { title: 'Personal Info', description: 'Basic personal information', component: PersonalInfoForm }, { title: 'Contact Details', description: 'Contact information', component: ContactForm }, { title: 'Review', description: 'Review your information', component: ReviewForm }]
const currentComponent = computed(() => { return formSteps[currentStep.value].component})
const isLastStep = computed(() => { return currentStep.value === formSteps.length - 1})
const nextStep = async () => { if (isLastStep.value) { await submitForm() } else { currentStep.value++ }}
const previousStep = () => { currentStep.value--}
const submitForm = async () => { submitting.value = true try { await submitData(formData) formStatus.value = 'finish' } catch (error) { formStatus.value = 'error' } finally { submitting.value = false }}</script>- Vertical Steps:
<template> <div class="grid grid-cols-3 gap-8"> <UISteps v-model="currentStep" :steps="steps" direction="vertical" class="h-full" > <template #step="{ step, status }"> <div class="p-4 rounded-lg" :class="{ 'bg-primary-50': status === 'process', 'bg-success-50': status === 'finish', 'bg-error-50': status === 'error' }" > <div class="font-medium"> {{ step.title }} </div> <div class="text-sm text-gray-500"> {{ step.description }} </div>
<div v-if="step.meta" class="mt-2 text-xs text-gray-400" > {{ step.meta }} </div> </div> </template> </UISteps>
<div class="col-span-2"> <component :is="stepComponents[currentStep]" v-bind="stepProps[currentStep]" @complete="handleStepComplete" /> </div> </div></template>
<script setup>const currentStep = ref(0)const steps = [ { title: 'Select Template', description: 'Choose a starting point', meta: '2 min' }, { title: 'Configure', description: 'Set up your project', meta: '5 min' }, { title: 'Deploy', description: 'Deploy your application', meta: '3 min' }]
const handleStepComplete = () => { if (currentStep.value < steps.length - 1) { currentStep.value++ }}</script>Best Practices
-
User Experience:
- Clear progression
- Visual feedback
- Intuitive navigation
- Error handling
-
Performance:
- Efficient updates
- Memory management
- Optimized rendering
- Smooth transitions
-
Accessibility:
- ARIA labels
- Keyboard navigation
- Focus management
- Screen reader support
-
Mobile:
- Responsive layout
- Touch targets
- Clear indicators
- Simplified interface
Common Use Cases
- Onboarding Flow:
<template> <div class="space-y-8"> <UISteps v-model="step" :steps="onboardingSteps" :progressDot="true" />
<div class="space-y-6"> <h2 class="text-2xl font-medium"> {{ currentStep.title }} </h2>
<p class="text-gray-600"> {{ currentStep.description }} </p>
<component :is="currentStep.component" @complete="nextStep" /> </div> </div></template>- Checkout Process:
<template> <div class="space-y-6"> <UISteps v-model="checkoutStep" :steps="checkoutSteps" :status="checkoutStatus" > <template #icon="{ step }"> <component :is="step.icon" class="w-6 h-6" /> </template> </UISteps>
<div class="grid md:grid-cols-3 gap-8"> <div class="md:col-span-2"> <component :is="currentComponent" v-model="checkoutData" @valid="stepValid = $event" /> </div>
<div> <OrderSummary :items="cart" :totals="totals" /> </div> </div> </div></template>- Installation Guide:
<template> <div class="grid grid-cols-4 gap-8"> <div> <UISteps v-model="currentStep" :steps="installSteps" direction="vertical" class="sticky top-4" /> </div>
<div class="col-span-3 space-y-8"> <div v-for="(step, index) in installSteps" :key="index" :ref="el => stepRefs[index] = el" class="space-y-4" > <h3 class="text-lg font-medium"> {{ step.title }} </h3>
<div class="prose"> <component :is="step.content" /> </div>
<CodeBlock v-if="step.code" :language="step.language" :code="step.code" /> </div> </div> </div></template>Component Composition
The UISteps component works well with:
- UIButton for navigation
- UIIcon for step icons
- UITooltip for help text
- UIProgress for progress
- UICard for step content
- UISpinner for loading states
- UIAlert for messages
- UIBadge for status