Playground

Simple

 

 

View Source 💻
<template>
  <form-wizard @on-complete="onComplete">
    <tab-content title="Personal details">
      My first tab content
    </tab-content>
    <tab-content title="Additional Info" >
      My second tab content
    </tab-content>
    <tab-content title="Last step" >
      Yuhuuu! This seems pretty damn simple
    </tab-content>
  </form-wizard>
</template>

<script>
import {FormWizard,TabContent} from "vue3-form-wizard";
import 'vue3-form-wizard/dist/style.css'
export default {
  components: {
    FormWizard,
    TabContent,
  },
  methods: {
     onComplete() {
      alert("Yay. Done!");
    },
  },
};
</script>

  

Small Tab size 🆕

 

 

View Source 💻
<template>
  <form-wizard @on-complete="onComplete" step-size="xs">
    <tab-content title="Personal details">
      You Can select 4 size for tabs : xs | sm | md | lg
    </tab-content>
    <tab-content title="Additional Info" >
      My second tab content
    </tab-content>
    <tab-content title="Last step" >
      Yuhuuu! This seems pretty damn simple
    </tab-content>
  </form-wizard>
</template>

<script>
import {FormWizard,TabContent} from "vue3-form-wizard";
import 'vue3-form-wizard/dist/style.css'
export default {
  components: {
    FormWizard,
    TabContent,
  },
  methods: {
     onComplete() {
      alert("Yay. Done!");
    },
  },
};
</script>

  

Square steps

 

 

View Source 💻
<template>
  <form-wizard @on-complete="onComplete" shape="square" color="#3498db">
    <tab-content title="Personal details">
      My first tab content
    </tab-content>
    <tab-content title="Additional Info" >
      My second tab content
    </tab-content>
    <tab-content title="Last step" >
      Yuhuuu! This seems pretty damn simple
    </tab-content>
  </form-wizard>
</template>

<script>
import { FormWizard, TabContent } from "vue3-form-wizard";
import "vue3-form-wizard/dist/style.css";
export default {
  components: {
    FormWizard,
    TabContent,
  },
  methods: {
     onComplete() {
      alert("Yay. Done!");
    },
  },
};
</script>

With font awsome icon 🆕

 

 

View Source 💻
<template>
  <FormWizard @on-complete="onComplete" color="#094899">
    <TabContent title="Personal details" icon="fa fa-user">
      My first tab content
    </TabContent>
    <TabContent title="Additional Info" icon="fa fa-gear">
      My second tab content
    </TabContent>
    <TabContent title="Last step" icon="fa fa-city">
      Yuhuuu! This seems pretty damn simple
    </TabContent>
  </FormWizard>
</template>

<script>
//local registration
import { FormWizard, TabContent } from "vue3-form-wizard";
import "vue3-form-wizard/dist/style.css";
export default {
  //component code
  components: {
    FormWizard,
    TabContent,
  },
  methods: {
     onComplete() {
      alert("Yay. Done!");
    },
  },
};
</script>
<style>
@import url("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css");
</style>

With Themify icon 🆕

 

 

View Source 💻
<template>
  <FormWizard @on-complete="onComplete" color="#c11c67">
    <TabContent title="Personal details" icon="ti-user">
      My first tab content
    </TabContent>
    <TabContent title="Additional Info" icon="ti-info-alt">
      My second tab content
    </TabContent>
    <TabContent title="Last step" icon="ti-credit-card">
      Yuhuuu! This seems pretty damn simple
    </TabContent>
  </FormWizard>
</template>

<script>
//local registration
import { FormWizard, TabContent } from "vue3-form-wizard";
import "vue3-form-wizard/dist/style.css";
export default {
  //component code
  components: {
    FormWizard,
    TabContent,
  },
  methods: {
    onComplete() {
      alert("Yay. Done!");
    },
  },
};
</script>
<style>
@import url("https://cdn.jsdelivr.net/gh/lykmapipo/themify-icons@0.1.2/css/themify-icons.css");
</style>

Custom icon 🆕

 

 

View Source 💻
<template>
  <FormWizard @on-complete="onComplete" color="#dd9c1d">
    <TabContent 
    title="Personal details" 
    customIcon="<img src='./vue.png' width='40px' />"
    >
    <div >
      You can add your HTML tag (svg,img,i,...) 
      <br>
      also can add your custom component (b-img,b-icon,....)
    </div>

    </TabContent>
    <TabContent
    title="Additional Info"  
    :customIcon="`<img  src='${viteIcon}' width='35px' />`" > 
      add your dynamic source
    </TabContent>
    <TabContent title="Last step" :customIcon="nuxtIcon">
      create tag in computed
    </TabContent>
  </FormWizard>
</template>

<script>
//local registration
import { FormWizard, TabContent } from "vue3-form-wizard";
import "vue3-form-wizard/dist/style.css";
export default {
    data() {
        return {
            viteIcon:'https://camo.githubusercontent.com/61e102d7c605ff91efedb9d7e47c1c4a07cef59d3e1da202fd74f4772122ca4e/68747470733a2f2f766974656a732e6465762f6c6f676f2e737667'
        }
    },
  //component code
  components: {
    FormWizard,
    TabContent,
  },
  computed:{
    nuxtIcon(){
        return `<img  src='https://nuxtjs.org/design-kit/colored-logo.svg' width='35px' />`
    }
  },
  methods: {
    onComplete() {
      alert("Yay. Done!");
    },
  },
};
</script>


compositon API 🆕

 

 

View Source 💻
<script setup>
//local registration
import { FormWizard, TabContent } from "vue3-form-wizard";
import "vue3-form-wizard/dist/style.css";

function onComplete() {
  alert("Yay. Done!");
}
</script>
<template>
  <FormWizard @on-complete="onComplete" color="#094899">
    <TabContent title="Personal details" icon="fa fa-user">
      My first tab content
    </TabContent>
    <TabContent title="Additional Info" icon="fa fa-gear">
      My second tab content
    </TabContent>
    <TabContent title="Last step" icon="fa fa-city">
      Yuhuuu! This seems pretty damn simple
    </TabContent>
  </FormWizard>
</template>

<style>
@import url("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css");
</style>


Step index

 

 

View Source 💻
<template>
 <form-wizard @on-complete="onComplete"
                      :start-index="1"
                      color="#e67e22">
            <tab-content title="Personal details"
                        >
              My first tab content
            </tab-content>
            <tab-content title="Additional Info"
                         >
              My second tab content
            </tab-content>
            <tab-content title="Last step"
                         >
              Yuhuuu! This seems pretty damn simple
            </tab-content>
        </form-wizard>
</template>

<script>
import {FormWizard,TabContent} from "vue3-form-wizard";
import 'vue3-form-wizard/dist/style.css'
export default {
  components: {
    FormWizard,
    TabContent,
  },
  methods: {
     onComplete() {
      alert("Yay. Done!");
    },
  },
};
</script>

Custom button and title text

 

 

View Source 💻
<template>
  <form-wizard
    @on-complete="onComplete"
    title="This is a new title"
    subtitle="And a new subtitle"
    shape="tab"
    back-button-text="Go back!"
    next-button-text="Go next!"
    finish-button-text="We're there"
    color="#9b59b6"
  >
    <tab-content title="Personal details">
      My first tab content
    </tab-content>
    <tab-content title="Additional Info" >
      My second tab content
    </tab-content>
    <tab-content title="Last step" >
      Yuhuuu! This seems pretty damn simple
    </tab-content>
  </form-wizard>
</template>

<script>
import { FormWizard, TabContent } from "vue3-form-wizard";
import "vue3-form-wizard/dist/style.css";
export default {
  name: "CustomButtonAndTitleText",
  components: {
    FormWizard,
    TabContent,
  },
  methods: {
     onComplete() {
      alert("Yay. Done!");
    },
  },
};
</script>

Custom title slot

 

This will replace my whole title

 

View Source 💻
<template>
  <form-wizard @on-complete="onComplete" shape="tab" color="#e67e22">
    <template #title>This will replace my whole title</template>

    <tab-content title="Personal details">
      My first tab content
    </tab-content>
    <tab-content title="Additional Info" >
      My second tab content
    </tab-content>
    <tab-content title="Last step" >
      Yuhuuu! This seems pretty damn simple
    </tab-content>
  </form-wizard>
</template>

<script>
import { FormWizard, TabContent } from "vue3-form-wizard";
import "vue3-form-wizard/dist/style.css";
export default {
  name: "CustomTitleSlost",
  components: {
    FormWizard,
    TabContent,
  },
  methods: {
     onComplete() {
      alert("Yay. Done!");
    },
  },
};
</script>


Call a function before tab switch

 

 

View Source 💻
<template>
  <form-wizard @on-complete="onComplete" shape="tab" color="#9b59b6">
    <tab-content
      title="Personal details"
     
      :before-change="beforeTabSwitch"
    >
      My first tab content
    </tab-content>
    <tab-content title="Additional Info" >
      My second tab content
    </tab-content>
    <tab-content title="Last step" >
      Yuhuuu! This seems pretty damn simple
    </tab-content>
  </form-wizard>
</template>

<script>
import { FormWizard, TabContent } from "vue3-form-wizard";
import "vue3-form-wizard/dist/style.css";
export default {
  name: "CallFunctionBeforeTabSwitch",

  methods: {
     onComplete() {
      alert("Yay. Done!");
    },
    beforeTabSwitch() {
      alert("This is called before switchind tabs");
      return true;
    },
  },
};
</script>


 

Custom Slot Title

 

View Source 💻
<template>
<FormWizard color="#9b59b6">
    <!-- Access WizardStep Component directly with all props  -->
    <template v-slot:step="props">
      <WizardStep
        :tab="props.tab"
        :transition="props.transition"
        :index="props.index"
        @click="props.navigateToTab(props.index)"
      >
        <small style="color: blue; font-size: 12px; margin: auto 2px">
          Tab{{ props.index + 1 }}
        </small>
      </WizardStep>
    </template>

    <!-- title slot  -->
    <template #title>
      <div><h3>Custom Slot Title</h3></div>
    </template>
    <TabContent title="First">
      <section class="form-wizard-sample-card">My first tab content</section>
    </TabContent>
    <TabContent title="Additional Info">
      <section class="form-wizard-sample-card">My second tab content</section>
    </TabContent>
    <TabContent title="Last step">
      <section class="form-wizard-sample-card">
        Yuhuuu! This seems pretty damn simple
      </section>
    </TabContent>

     <!-- Access footer  directly with all props options -->
     <!-- You can create custom design and event -->
    <template v-slot:footer="props">
      <div class="wizard-footer-left">
        <button
          v-if="props.activeTabIndex > 0 && !props.isLastStep"
          :style="props.fillButtonStyle"
          @click.native="props.prevTab()"
          class="wizard-button"
        >
          Previous test
        </button>
      </div>
      <div class="wizard-footer-right">
        <button
          v-if="!props.isLastStep"
          @click.native="props.nextTab()"
          class="wizard-button"
          :style="props.fillButtonStyle"
        >
          Next
        </button>

        <button
          v-else
          @click.native="confirmMethod"
          class="finish-button"
          :style="props.fillButtonStyle"
        >
          {{ props.isLastStep ? "Done" : "Next" }}
        </button>
      </div>
    </template>
  </FormWizard>
</template>
<script>
import { FormWizard, TabContent, WizardStep } from "vue3-form-wizard";
import "vue3-form-wizard/dist/style.css";
export default {
  name: "CustomSlotProps",
  components: {
    FormWizard,
    TabContent,
    WizardStep,
  },

  methods: {
    confirmMethod() {
      alert("Done");
    },
  },
};
</script>
<style>
.wizard-card-footer .wizard-footer-left .wizard-button {
  padding: 10px;
  border-radius: 6px;
  cursor: pointer;
}
.wizard-card-footer .wizard-footer-right .wizard-button {
  padding: 10px;
  border-radius: 6px;
  cursor: pointer;
}
.wizard-card-footer .wizard-footer-right .finish-button {
  padding: 15px 25px;
  border-radius: 6px;
  cursor: pointer;
  background-color: green !important;
  border: 0;
}
.form-wizard-sample-card {
  max-width: 300px;
  text-align: center;
  padding: 30px 10px;
  box-shadow: 0px 4px 8px #eee;
  margin: 10px auto;
  border-radius: 10px;
}
</style>


Schema mode (V1) 🆕

Schema mode lets you define steps declaratively with schema, schema-components, and v-model. See the Schema documentation for details.

Schema: Basic

 

Simple Step

This is a simple step For Schema Basic

 

View Source 💻
<template>
  <FormWizard
    title="Schema: Basic"
    :schema="schema"
    :schema-components="schemaComponents"
    v-model="schemaData"
    color="#9b59b6"
    @on-complete="onComplete"
  />
</template>

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

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

const schemaComponents = { IntroStep, ReviewStep };
const schemaData = ref({ plan: "basic" });
const onComplete = () => alert("Done!");
</script>

Schema: Conditional Steps

Shows steps based on condition. The Premium step appears only when plan is "premium".

 

Choose plan

Select a plan to continue.

 

View Source 💻
<template>
  <FormWizard
    title="Schema: Conditional Steps"
    :schema="schema"
    :schema-components="schemaComponents"
    v-model="schemaData"
    @on-complete="onComplete"
  />
</template>

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

import IntroStep from "./schema-steps/IntroStep.vue";
import PremiumStep from "./schema-steps/PremiumStep.vue";
import ReviewStep from "./schema-steps/ReviewStep.vue";

const schema = {
  initialData: { plan: "basic" },
  steps: [
    { id: "intro", title: "Intro", component: "IntroStep" },
    {
      id: "premium",
      title: "Premium",
      component: "PremiumStep",
      condition: ({ data }) => data.plan === "premium",
    },
    {
      id: "review",
      title: "Review",
      component: "ReviewStep",
      validate: ({ data }) => (data.plan ? true : "Select a plan"),
    },
  ],
};

const schemaComponents = { IntroStep, PremiumStep, ReviewStep };
const schemaData = ref({ plan: "basic" });
const onComplete = () => alert("Done!");
</script>

Schema: Async Validation

Use validate to block navigation and show custom error messages.

 

Enter email

 

View Source 💻
<template>
  <FormWizard
    title="Schema: Async Validation"
    :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 EmailStep from "./schema-steps/EmailStep.vue";
import DoneStep from "./schema-steps/DoneStep.vue";

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

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

Schema: Render Function

Step components defined as plain render functions (functional components using h()).

 

Step A

Built with render function (h).

 

View Source 💻
<template>
  <FormWizard
    title="Schema: Render Function"
    :schema="schema"
    :schema-components="schemaComponents"
    v-model="schemaData"
    @on-complete="onComplete"
  />
</template>

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

const StepA = (props) =>
  h("div", [
    h("h2", "Step A"),
    h("input", {
      value: props.data.value,
      onInput: (e) => props.updateData({ value: e.target.value }),
    }),
  ]);

const StepB = (props) => h("div", [h("h2", "Step B"), h("p", props.data.value)]);

const schema = {
  initialData: { value: "" },
  steps: [
    { id: "a", title: "Input", component: "StepA" },
    { id: "b", title: "Result", component: "StepB" },
  ],
};

const schemaComponents = { StepA, StepB };
const schemaData = ref({ value: "" });
</script>

Schema: defineComponent

Step components defined with defineComponent and setup returning a render function.

 

Your name

 

View Source 💻
<template>
  <FormWizard
    title="Schema: defineComponent"
    :schema="schema"
    :schema-components="schemaComponents"
    v-model="schemaData"
    @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'

const NameStep = defineComponent({
  name: "NameStep",
  props: { data: Object, updateData: Function },
  setup(props) {
    return () =>
      h("div", [
        h("input", {
          value: props.data.name,
          onInput: (e) => props.updateData({ name: e.target.value }),
        }),
      ]);
  },
});

const SummaryStep = defineComponent({
  name: "SummaryStep",
  props: { data: Object, updateData: Function },
  setup(props) {
    return () => h("div", [h("p", "Hello, " + (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: "" });
</script>

Async validation with error message

before-change beforeChange (): boolean | Promise<boolean> can accept a promise that resolves with a boolean. Resolving with a truthy value, will trigger the navigation to next step. Rejecting with a message, will set an internal message that can be handled and displayed if needed.

 

 

View Source 💻
<template>
  <form-wizard
    @on-complete="onComplete"
    @on-loading="setLoading"
    @on-validate="handleValidation"
    @on-error="handleErrorMessage"
    shape="circle"
    color="gray"
    error-color="#e74c3c"
  >
    <tab-content
      title="Personal details"
      :before-change="validateAsync"
      icon="ti-user"
    >
      First tab
    </tab-content>
    <tab-content title="Additional Info" icon="ti-settings">
      Second tab
    </tab-content>
    <tab-content title="Last step" icon="ti-check"> Third tab </tab-content>

    <div class="loader" v-if="loadingWizard"></div>
    <div v-if="errorMsg">
      <span class="error">{{ errorMsg }}</span>
    </div>
  </form-wizard>
</template>

<script setup>
import { ref } from "vue";
//local registration
import { FormWizard, TabContent } from "vue3-form-wizard";

const loadingWizard = ref(false);
const errorMsg = ref("");
const count = ref(0);
const onComplete = () => {
  alert("Yay. Done!");
};
const setLoading = (value) => {
  loadingWizard.value = value;
};
const handleValidation = (isValid, tabIndex) => {
  console.log("Tab: " + tabIndex + " valid: " + isValid);
};
const handleErrorMessage = (err) => {
  errorMsg.value = err;
};
const validateAsync = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (count.value < 1) {
        count.value++;
        reject(
          "This is a custom validation error message. Click next again to get rid of the validation"
        );
      } else {
        count.value = 0;
        resolve(true);
      }
    }, 1000);
  });
};
</script>
<style>
@import url("https://unpkg.com/vue3-form-wizard/dist/style.css");
span.error {
  color: #e74c3c;
  font-size: 20px;
  display: flex;
  justify-content: center;
}
/* This is a css loader. It's not related to vue-form-wizard */
.loader,
.loader:after {
  border-radius: 50%;
  width: 10em;
  height: 10em;
}
.loader {
  margin: 60px auto;
  font-size: 10px;
  position: relative;
  text-indent: -9999em;
  border-top: 1.1em solid rgba(255, 255, 255, 0.2);
  border-right: 1.1em solid rgba(255, 255, 255, 0.2);
  border-bottom: 1.1em solid rgba(255, 255, 255, 0.2);
  border-left: 1.1em solid #e74c3c;
  -webkit-transform: translateZ(0);
  -ms-transform: translateZ(0);
  transform: translateZ(0);
  -webkit-animation: load8 1.1s infinite linear;
  animation: load8 1.1s infinite linear;
}
@-webkit-keyframes load8 {
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}
@keyframes load8 {
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}
</style>



Complete form example integrated with vue-form-generator

Element UI form integration

Vuelidate integration

Dynamic components for tabs

Vue router integration

Async validation with error

Customized buttons with scoped slot

Last Updated:
Contributors: parsa jiravand, parsajiravand