Schema Mode new v1

Schema mode is a declarative API introduced in v1.0.0 that lets you define wizard steps via a schema prop instead of <tab-content> children. Use it when you prefer configuration over markup.

Overview

When you provide a schema prop and no <tab-content> children, FormWizard runs in schema mode:

  • Define steps in a single schema object
  • Use condition to hide steps dynamically
  • Use validate to block navigation with custom error messages
  • Share wizard data with v-model and schema-components

Demos

Try the schema mode demos on the Demos page:

Basic Usage

<template>
  <form-wizard
    :schema="schema"
    :schema-components="schemaComponents"
    v-model="wizardData"
    @on-complete="handleComplete"
  />
</template>

<script setup>
import { ref } from 'vue'
import { FormWizard } from 'vue3-form-wizard'
import 'vue3-form-wizard/dist/style.css'
import IntroStep from './IntroStep.vue'
import ReviewStep from './ReviewStep.vue'

const schema = {
  initialData: { plan: 'basic' },
  steps: [
    { id: 'intro', title: 'Intro', component: 'IntroStep' },
    { id: 'review', title: 'Review', component: 'ReviewStep' },
  ],
}

const schemaComponents = { IntroStep, ReviewStep }
const wizardData = ref({ plan: 'basic' })
const handleComplete = () => alert('Done!')
</script>

Schema Structure

FormWizardSchema

PropertyTypeDescription
initialDataWizardDataInitial data for the wizard (see below)
stepsFormWizardStep[]Array of step definitions

initialData

initialData is the seed state for the wizard. It defines the shape and starting values of the shared data that all step components read and update.

AspectDetails
PurposeProvides the initial values for data and v-model. All steps share this object.
TypePlain object with string keys. WizardData is Record<string, any>.
UsageStep components receive data (current state) and updateData (to mutate it). Changes are merged into the wizard data and emitted via update:modelValue.
Sync with v-modelIf you pass v-model="wizardData", FormWizard keeps initialData and wizardData in sync. Use initialData in the schema and ref(schema.initialData) or equivalent for the bound value.
ReactivityWhen initialData values change (e.g. via updateData), condition is re-run and step components get updated data props.

Example

const initial = { plan: 'basic', email: '', agreedToTerms: false }

const schema = {
  initialData: initial,
  steps: [
    { id: 'plan', title: 'Plan', component: 'PlanStep' },     // reads/writes data.plan
    { id: 'contact', title: 'Contact', component: 'ContactStep' },  // reads/writes data.email
    { id: 'review', title: 'Review', component: 'ReviewStep' },     // reads all
  ],
}

const wizardData = ref({ ...initial })

Bind with v-model="wizardData" to read or reset the data from the parent (e.g. after submit or when reopening the wizard).

FormWizardStep

PropertyTypeDescription
idstringUnique step identifier
titlestringStep title shown in the step indicator
componentstringKey in schema-components map (e.g. 'IntroStep')
iconstring(Optional) Icon name (e.g. themify, font-awesome)
customIconstring(Optional) Custom icon HTML
routestring | RouteLocationRaw(Optional) Route for Vue Router sync
condition(ctx) => boolean | Promise<boolean>(Optional) Hide step when returns false
validate(ctx) => boolean | string | Promise<boolean | string>(Optional) Block navigation; return true to allow, or error string

Validation Context

Both condition and validate receive a context object:

{
  data: WizardData      // Current wizard data (reactive)
}
  • condition: Re-evaluated when wizardData changes. Steps are hidden when it returns false.
  • validate: Runs before leaving the step. Return true to allow navigation, or a string for an error message.

Step Component Props

Each step component receives (see Schema: Basic and Schema: Render Function for examples):

PropTypeDescription
dataWizardDataCurrent wizard data
updateData(partial: Partial<WizardData>) => voidUpdate wizard data (use with v-model or direct calls)

Bind inputs with updateData for two-way sync:

<template>
  <div>
    <label>Plan:</label>
    <select :value="data.plan" @change="onPlanChange">
      <option value="basic">Basic</option>
      <option value="premium">Premium</option>
    </select>
  </div>
</template>

<script setup>
defineProps(['data', 'updateData'])

const onPlanChange = (e) => {
  props.updateData({ plan: e.target.value })
}
</script>

Conditional Steps

Use condition to show steps only when criteria are met. See Schema: Conditional Steps demo.

const schema = {
  initialData: { plan: 'basic' },
  steps: [
    { id: 'intro', title: 'Intro', component: 'IntroStep' },
    {
      id: 'premium',
      title: 'Premium',
      component: 'PremiumStep',
      condition: ({ data }) => data.plan === 'premium',  // Only for premium plan
    },
    { id: 'review', title: 'Review', component: 'ReviewStep' },
  ],
}

Async Validation

validate can be async. Return a string to show an error message. See Schema: Async Validation demo.

{
  id: 'email',
  title: 'Email',
  component: 'EmailStep',
  validate: ({ data }) => {
    const ok = /^[^@]+@[^@]+\.\w+$/.test(data.email || '')
    return ok ? true : 'Enter a valid email'
  },
}

Backward Compatibility

Schema mode is optional. Omit schema to keep using the classic slot-based flow with <tab-content>. Both modes can coexist in different parts of your app.

Last Updated:
Contributors: parsajiravand