Skip to content

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 index
  • steps (array): Steps configuration
  • direction (‘horizontal’ | ‘vertical’): Steps direction
  • size (‘sm’ | ‘md’ | ‘lg’): Component size
  • status (string): Overall status
  • progressDot (boolean): Use dot style
  • responsive (boolean): Enable responsive mode
  • labelPlacement (‘horizontal’ | ‘vertical’): Label placement
  • initial (number): Initial step
  • current (number): Current step
  • processStatus (string): Process step status
  • finishStatus (string): Finish step status
  • alignCenter (boolean): Center alignment

Events

  • update:modelValue: Step changed
  • change: Step changed
  • finish: Process finished
  • error: Error occurred
  • click: Step clicked

Slots

  • step: Custom step content
  • title: Custom title content
  • description: Custom description content
  • icon: Custom icon content
  • progress: Custom progress content
  • tail: Custom tail content

Usage Examples

  1. 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>
  1. 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>
  1. 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

  1. User Experience:

    • Clear progression
    • Visual feedback
    • Intuitive navigation
    • Error handling
  2. Performance:

    • Efficient updates
    • Memory management
    • Optimized rendering
    • Smooth transitions
  3. Accessibility:

    • ARIA labels
    • Keyboard navigation
    • Focus management
    • Screen reader support
  4. Mobile:

    • Responsive layout
    • Touch targets
    • Clear indicators
    • Simplified interface

Common Use Cases

  1. 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>
  1. 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>
  1. 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