Skip to content
Star

Multi-Step Form Wizard with Zod Schema Validation

Enterprise registration flows, project setups, or complex checkout journeys are typically divided into multiple steps to avoid overwhelming users. A critical engineering requirement is ensuring that each step strictly validates its input before allowing progression, preventing partial or invalid states from reaching the server.

This recipe teaches how to integrate PxFormWizard with Zod for robust, typed validation per step.


Interactive Demonstration

Try advancing without filling in the required fields: PxFormWizard intercepts navigation, displays a focused error message, and retains step state. Upon completing the final step, the validated payload is emitted.

Multi-Step Registration Wizard with Step-Level Validation

Patterns & Best Practices

1. safeParse instead of parse

When validating user forms reactively, avoid letting Zod throw uncaught exceptions. Use safeParse():

ts
const result = accountSchema.safeParse(data)
if (!result.success) {
  // Return the first human-readable error message to display
  return result.error.issues[0]?.message
}
return true

2. Persisting Progress to localStorage

To prevent user frustration from accidental page reloads or network drops, synchronize wizard state with useStorage from @vueuse/core:

ts
import { useStorage } from '@vueuse/core'

const formState = useStorage('praxis_onboarding_draft', {
  fullName: '',
  email: '',
  orgName: '',
  role: '',
  plan: 'pro'
})

// Clear draft on successful completion:
function onWizardComplete() {
  localStorage.removeItem('praxis_onboarding_draft')
}

3. Asynchronous Validation (Username / Subdomain Uniqueness)

If you need to check whether an organization name or subdomain is available on the server before proceeding to step 2, step validate functions can be async:

ts
validate: async (data) => {
  const isAvailable = await checkOrgAvailability(data.orgName)
  if (!isAvailable) {
    return 'This organization name is already taken'
  }
  return true
}

Built with VitePress