@doderasoftware/vue-stepper
v1.0.9
Published
A headless, accessible, and fully customizable multi-step stepper component for Vue 3. Build complex forms, wizards, and onboarding flows with ease. Features TypeScript support, responsive mobile design, step validation, optional steps, progress tracking,
Maintainers
Readme
@doderasoftware/vue-stepper
A headless, accessible, and fully customizable multi-step stepper component for Vue 3. Build complex forms, wizards, and onboarding flows with ease.
📖 Documentation | 📦 npm | 🐛 Issues
✨ Features
- 🎨 Fully Customizable - Complete UI control via
:uiprop system - 🌙 Dark Mode - Built-in dark mode support (follows system preference)
- 📱 Responsive - Sidebar collapses on mobile with dropdown menu
- 🔒 Step Locking - Prevent navigation to specific steps
- ⏭️ Optional Steps - Mark steps as optional with visual indicators
- 📊 Progress Tracking - Automatic step counters and progress bars
- 🎯 TypeScript - Full type safety out of the box
- 🪶 Zero Dependencies - No external CSS frameworks required
- 🎛️ Headless Ready - Use your own styles or the built-in ones
📦 Installation
npm install @doderasoftware/vue-stepperThat's it! Styles are automatically included - no additional CSS imports needed.
🚀 Quick Start
<template>
<Stepper
v-model="currentStep"
:steps="steps"
:can-go-next="canProceed"
sidebar-title="Registration"
@complete="handleComplete"
>
<template #default="{ currentStep: stepIndex }">
<PersonalInfo v-if="stepIndex === 0" v-model="formData.personal" />
<AccountDetails v-else-if="stepIndex === 1" v-model="formData.account" />
<Review v-else-if="stepIndex === 2" :data="formData" />
</template>
</Stepper>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { Stepper } from '@doderasoftware/vue-stepper'
const currentStep = ref(0)
const formData = ref({ personal: {}, account: {} })
const steps = [
{ category: 'Step 1', title: 'Personal Info', description: 'Enter your personal details' },
{ category: 'Step 2', title: 'Account Details', optional: true },
{ category: 'Step 3', title: 'Review & Submit' },
]
const canProceed = computed(() => {
if (currentStep.value === 0) {
return !!formData.value.personal.email
}
return true
})
function handleComplete() {
console.log('Form submitted:', formData.value)
}
</script>🌙 Dark Mode
The component includes full dark mode support out of the box. It uses class-based dark mode - add the dark class to your <html> or <body> element to enable dark styles.
<!-- Enable dark mode via class on html/body -->
<html class="dark">
...
</html>This approach is compatible with most dark mode implementations (Tailwind, manual toggle, etc.).
🎨 UI Customization
The :ui prop allows complete control over every element's styling. Pass class names to override the defaults:
<template>
<Stepper
v-model="currentStep"
:steps="steps"
:ui="{
root: 'my-stepper',
sidebar: 'bg-indigo-50 border-indigo-200',
navItemCurrent: 'bg-indigo-600 text-white',
indicatorCompleted: 'bg-indigo-600',
buttonPrimary: 'bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700',
progressFill: 'bg-indigo-600',
}"
>
<!-- your content -->
</Stepper>
</template>Available UI Keys
| Key | Description |
|-----|-------------|
| root | Root container |
| sidebar | Desktop sidebar container |
| sidebarContent | Sidebar inner content |
| sidebarTitle | Sidebar title text |
| nav | Navigation container |
| navItem | Step button - base |
| navItemCurrent | Step button - current |
| navItemCompleted | Step button - completed |
| navItemDisabled | Step button - disabled |
| indicator | Step indicator - base |
| indicatorCurrent | Step indicator - current |
| indicatorCompleted | Step indicator - completed |
| indicatorDisabled | Step indicator - disabled |
| stepCategory | Step category text |
| stepTitle | Step title text |
| progressBar | Progress bar track |
| progressFill | Progress bar fill |
| main | Main content area |
| card | Content card |
| navigation | Navigation buttons container |
| button | Button base |
| buttonPrimary | Primary button (next/complete) |
| buttonSecondary | Secondary button (back) |
| buttonDisabled | Disabled button state |
📋 Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| modelValue | number | 0 | Current step index (v-model) |
| steps | StepConfig[] | required | Array of step configurations |
| ui | StepperUI | {} | UI customization object |
| sidebarTitle | string | 'Steps' | Title displayed above the sidebar |
| canGoNext | boolean | true | Whether user can proceed to next step |
| nextButtonText | string | 'Continue' | Text for the next button |
| backButtonText | string | 'Back' | Text for the back button |
| completeButtonText | string | 'Complete' | Text for the final step button |
| showNavigation | boolean | true | Show/hide navigation buttons |
| showProgress | boolean | true | Show/hide progress indicator |
| showMobileHeader | boolean | true | Show/hide mobile header |
| showSidebar | boolean | true | Show/hide desktop sidebar |
| linear | boolean | true | Restrict navigation to sequential steps only |
📡 Events
| Event | Payload | Description |
|-------|---------|-------------|
| update:modelValue | number | Emitted when step index changes |
| step-change | { from: number, to: number } | Emitted on step navigation |
| next | StepChangeEvent | Emitted when next button is clicked |
| back | StepChangeEvent | Emitted when back button is clicked |
| complete | void | Emitted when completing the last step |
🎰 Slots
| Slot | Props | Description |
|------|-------|-------------|
| default | { currentStep, currentStepConfig, goNext, goBack, ... } | Main content area |
| header | - | Content above the step container |
| title | - | Title area inside step container |
| sidebar-header | - | Custom sidebar header |
| sidebar-footer | - | Custom sidebar footer |
| step-indicator | { step, index, isCurrent, isCompleted, isDisabled } | Custom step indicator |
| back-icon | - | Custom back button icon |
| next-icon | - | Custom next button icon |
📐 Step Configuration
interface StepConfig {
id?: string | number
category: string
title: string
disabled?: boolean
optional?: boolean
}🪝 Composable
Use the useMultiStep composable for headless control:
import { useMultiStep } from '@doderasoftware/vue-stepper'
const {
currentStep,
isFirstStep,
isLastStep,
progress,
goNext,
goBack,
goToStep,
isStepCompleted,
reset,
} = useMultiStep({
steps: mySteps,
initialStep: 0,
linear: true,
onStepChange: ({ from, to }) => console.log(`Step changed from ${from} to ${to}`),
})🔗 Links
📄 License
MIT © Dodera Software SRL
