Vue3 form wizard

March 16, 2026 Β· View on GitHub

A dynamic wizard to split your forms easier

Vue3-form-wizard is a vue based component with no external depenendcies which simplifies tab wizard management.

**πŸ“šDocument ・ πŸ”Ž Demos ・ πŸ”¬ Playground**

Dependencies

  • required: Vuejs >= 3.x

Installation

npm install vue3-form-wizard --save
yarn add vue3-form-wizard

πŸš€ Features

  • Schema mode: Declarative steps with schema, condition, validate, and v-model
  • Classic mode: Slot-based steps with <tab-content>
  • Vue Router: URL sync with route prop (optional)
  • Accessibility: ARIA roles, keyboard navigation
  • TypeScript: Full type support

πŸ”§ Document

Quick start

<script setup>
import { FormWizard, TabContent } from 'vue3-form-wizard'
import 'vue3-form-wizard/dist/style.css'

const onComplete = () => alert('Done!')
</script>

<template>
  <form-wizard @on-complete="onComplete" color="#9b59b6">
    <tab-content title="Step 1">
      <p>First step content.</p>
    </tab-content>
    <tab-content title="Step 2">
      <p>Second step content.</p>
    </tab-content>
    <tab-content title="Step 3">
      <p>Final step.</p>
    </tab-content>
  </form-wizard>
</template>

Register globally or use components locally; include the CSS. See Schema mode and Router Integration for more.

πŸ”— Router Integration

Vue3 Form Wizard now supports automatic route synchronization with Vue Router!

Setup

First, install Vue Router:

npm install vue-router@4.1.6

Configure your Vue app with Vue Router:

import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    // Your routes
  ]
})

const app = createApp(App)
app.use(router)
app.mount('#app')

Usage

Add route prop to your tab-content components:

<template>
  <form-wizard @on-complete="onComplete" color="#9b59b6">
    <tab-content title="Step 1" route="/step1">
      Content for step 1
    </tab-content>
    <tab-content title="Step 2" route="/step2">
      Content for step 2
    </tab-content>
    <tab-content title="Step 3" route="/step3">
      Content for step 3
    </tab-content>
  </form-wizard>
</template>

Features

  • βœ… Automatic Navigation: Tab changes update the URL
  • βœ… URL Sync: Direct URL access navigates to the correct tab
  • βœ… Browser History: Back/forward buttons work correctly
  • βœ… Deep Linking: Shareable URLs for specific wizard steps

Route Types

The route prop accepts:

  • String: route="/step1" - Direct path
  • Object: route="{ name: 'step1', params: { id: 1 } }" - Named routes with params

Schema Mode

Define wizard steps declaratively with conditions and validation:


<template>
  <FormWizard
    title="Schema: Basic"
    :schema="schema"
    :schema-components="schemaComponents"
    v-model="schemaData"
    @on-complete="onComplete"
  />
</template>

<script setup>
import { ref } from "vue";
import { FormWizard } from "vue3-form-wizard";
import "vue3-form-wizard/dist/style.css";
import SimpleStep from "./schema-steps/SimpleStep.vue";
import DoneStep from "./schema-steps/DoneStep.vue";

const schema = {
  initialData: { plan: "basic" },
  steps: [
    { id: "intro", title: "Intro", component: "SimpleStep" },
    { id: "review", title: "Review", component: "DoneStep" },
  ],
};

const schemaComponents = { SimpleStep, DoneStep };
const schemaData = ref();
const onComplete = () => alert("Done!");
</script>

Schema mode with DefineComponent

<template>
  <FormWizard
    title="Schema: defineComponent"
    :schema="schema"
    :schema-components="schemaComponents"
    v-model="schemaData"
    color="#8e44ad"
    @on-complete="onComplete"
  />
</template>

<script setup>
import { ref, defineComponent, h } from "vue";
import { FormWizard } from "vue3-form-wizard";
import "vue3-form-wizard/dist/style.css";

// Step components using defineComponent with setup returning render function
const NameStep = defineComponent({
  name: "NameStep",
  props: {
    data: { type: Object, required: true },
    updateData: { type: Function, required: true },
  },
  setup(props) {
    return () =>
      h("div", [
        h("h2", "Your name"),
        h("input", {
          type: "text",
          value: props.data.name,
          placeholder: "Name",
          onInput: (e) => props.updateData({ name: e.target.value }),
          style: "padding:8px;width:100%;max-width:240px;margin-bottom:8px;",
        }),
      ]);
  },
});

const SummaryStep = defineComponent({
  name: "SummaryStep",
  props: {
    data: { type: Object, required: true },
    updateData: { type: Function, required: true },
  },
  setup(props) {
    return () =>
      h("div", [
        h("h2", "Summary"),
        h("p", ["Hello, ", h("strong", props.data.name || "Guest"), "!"]),
      ]);
  },
});

const schema = {
  initialData: { name: "" },
  steps: [
    { id: "name", title: "Name", component: "NameStep" },
    { id: "summary", title: "Summary", component: "SummaryStep" },
  ],
};

const schemaComponents = { NameStep, SummaryStep };
const schemaData = ref({ name: "" });
const onComplete = () => alert("Done!");
</script>

Step components receive data and update-data props. Use condition to hide steps dynamically and validate to block navigation.

RTL support

You can enable RTL for the wizard content without flipping the steps: If you also want the horizontal steps, progress bar, and footer buttons to run from right to left, use reverse-horizontal and rtl together.

Sample Demo: RTL support

Local Samples & Tests

Run the dev server for 15 samples:

npm run dev

Visit http://localhost:5173 and use the dropdown to switch between samples: basic wizard, icons, layouts, shapes, validation, schema mode, and more.

Run tests:

npm run test

Scripts

CommandDescription
npm run devStart dev server with samples
npm run buildBuild library and types
npm run testRun Vitest test suite

Credits

Cloned from vue-form-wizard, updated to Vue 3 with new features and bug fixes.