Wizards
useWizard(steps, options?) is a composable for multi-step forms built on top of defineMachine. The wizard machine is generated automatically from the steps array.
function useWizard<TContext>(
steps: WizardStep<TContext>[],
options?: WizardOptions,
): WizardInstance<TContext>Wizard steps
WizardStep fields:
id
string. Unique step identifier (becomes a state name internally).
label
string?. Display label.
component
Component?. Vue component to render for this step.
canProceed
(ctx) => boolean | Promise<boolean>. Gate for next() and forward goTo(); may be async. Receives the wizard's real, live context — whatever onEnter/onLeave have merged into it so far.
onEnter
(ctx) => void | Partial<TContext>. Called when the wizard enters this step. Return a partial to merge it into context — e.g. seed a default.
onLeave
(ctx) => void | Partial<TContext>. Called when the wizard leaves this step. Return a partial to merge it into context — the usual place to persist a step's collected data (e.g. form fields) before canProceed on the next step reads it.
Wizard options
WizardOptions fields:
id
string? · default: auto-generated. Machine id registered in the MachineStore (when VueMachinePlugin is installed) and shown in DevTools. Set this explicitly if you need a stable, predictable id (e.g. to look the wizard up via useMachineStore().get(id)); otherwise each useWizard() gets its own unique id automatically.
initialStep
number · default: 0. Index of the starting step.
allowSkip
boolean · default: false. Skip canProceed on forward goTo().
circular
boolean · default: false. next() wraps from last step back to first.
Return value
currentStep
Ref<WizardStep>. Currently active step object.
currentIndex
ComputedRef<number>. Zero-based index of the current step.
totalSteps
number. Total number of steps.
progress
ComputedRef<number>. 0 to 1 based on current index.
isFirst
ComputedRef<boolean>. true on the first step.
isLast
ComputedRef<boolean>. true on the last step.
history
Ref<string[]>. IDs of visited steps.
context
Readonly<Ref<TContext>>. Accumulated context — whatever onEnter/onLeave have merged in so far.
next()
Promise<boolean>. Advance; calls canProceed first; returns false if blocked.
prev()
void. Go back (no guard).
goTo(id)
Promise<boolean>. Jump to step by id; respects canProceed unless allowSkip.
reset()
void. Return to the initial step.
Example
<script setup lang="ts">
import { useWizard } from '@macrulez/vue-state-machine'
import type { WizardStep } from '@macrulez/vue-state-machine'
import StepInfo from './StepInfo.vue'
import StepAddress from './StepAddress.vue'
import StepPayment from './StepPayment.vue'
interface CheckoutCtx {
name: string
email: string
address: string
}
const steps: WizardStep<CheckoutCtx>[] = [
{
id: 'info',
label: 'Your info',
component: StepInfo,
// Populated by StepInfo's own onLeave below before this ever runs on
// the *next* step's canProceed check — see "context" in the return
// value above.
canProceed: (ctx) => !!ctx.name && !!ctx.email,
},
{
id: 'address',
label: 'Delivery',
component: StepAddress,
canProceed: (ctx) => !!ctx.address,
// Return a partial to merge collected form data into context — e.g. read
// from a ref StepAddress updates via v-model, or from an emit it fires.
onLeave: (): Partial<CheckoutCtx> => ({ address: addressFieldRef.value }),
},
{
id: 'payment',
label: 'Payment',
component: StepPayment,
onEnter: () => trackEvent('payment_step_entered'),
},
]
const { currentStep, context, progress, isFirst, isLast, next, prev } = useWizard(steps)
</script>
<template>
<div>
<progress :value="progress" max="1" />
<component :is="currentStep.component" :context="context" />
<nav>
<button :disabled="isFirst" @click="prev">Back</button>
<button v-if="!isLast" @click="next">Next</button>
<button v-else @click="submit">Place order</button>
</nav>
</div>
</template>Rules for canProceed
- May return a
booleanor aPromise<boolean> - If it returns
false,next()/ forwardgoTo()returnfalseand the wizard stays on the current step - If it throws, the same outcome —
falseis returned, the error is logged toconsole.errorin dev mode prev()and backwardgoTo()never checkcanProceedallowSkip: truedisablescanProceedforgoTo()only;next()always checks it