DEV Community

Yogesh Galav
Yogesh Galav

Posted on • Edited on

How to build Multi Step Form in Vue3

Hey folks! Hope you are doing well. This blog is about creating a multi step form in Vue.js with help of slots.
If you wanna skip the read or directly import the component here's the link
https://github.com/yogeshgalav/vue-multi-step-form
https://www.npmjs.com/package/vue-multi-step-form

Slots

Slots in simple language are used to inherit or include code from parent component were it will be used.
Hence you can decide according to your needs what code you would like to show or embed in child component directly from parent component.
Hence you have a dynamic component with dynamic html or template or text.
Mostly we use slots when we want to pass html or template to child component because passing it via props and then embedding it wouldn't be an ideal thing to do.

If you want to study more here is the link to official docs:
https://vuejs.org/guide/components/slots.html

Features

Now back to the Multi Step Form
Here are some features that we intend to implement

  1. It should support n number of steps.
  2. It should jump to next step when enter key pressed or submit button pressed.
  3. Header and Footer should be modifiable via slots as well.
  4. Step can be skippable.
  5. Prevent next step when current step is invalid.
  6. Form could be submit by html action attribute as well.

Architecture

Here's how our form will look in html

<form>
<div id="step1">
<input type="text" name="phone_no">
</div>
<div id="step2">
<input type="text" name="otp">
</div>
<div id="step3">
<input type="text" name="first_name">
<input type="text" name="last_name">
</div>
</form>
Enter fullscreen mode Exit fullscreen mode

And here's how our form will look in vue

LoginPage.vue

<template>
  <MultiStepForm>
    <template #step1>
      <input type="text" name="phone_no">
    </template>
    <template #step2>
      <input type="text" name="otp">
    </template>
    <template #step3>
      <input type="text" name="first_name">
      <input type="text" name="last_name">
    </template>
  </MultiStepForm>
</template>

Enter fullscreen mode Exit fullscreen mode

MultiStepForm.vue

<template>
  <form>
    <slot name="step1" />
    <slot name="step2" />
    <slot name="step3" />
  </form>
</template>
Enter fullscreen mode Exit fullscreen mode

Implementation

Feature 1
For our component to support 'N' number of steps we just need an array to loop over. That array we will pass from parent component via props.

MultiStepForm.vue

<template>
  <form>
    <div 
     v-for="(step, index) in steps" 
     :key="index" 
     :id="'step'+(index+1)" 
     v-show="activeStepIndex===index"
     class="vue-form-step">

       <slot :name="'step'+(index+1)" />

     </div>
  </form>
</template>
Enter fullscreen mode Exit fullscreen mode
const props = defineProps(['steps']);
let activeStepIndex = ref(0);
Enter fullscreen mode Exit fullscreen mode

Feature 2
For moving on to next step in form we just need to increase 'activeStepIndex' on submit button press.

MultiStepForm.vue

  <form @submit.prevent="submitStep">
    <div 
     v-for="(step, index) in steps" 
     :key="index" 
     :id="'step'+(index+1)" 
     v-show="activeStepIndex===index"
     class="vue-form-step">

       <slot :name="'step'+(index+1)" />

     </div>
    <button type="submit">Next</button>
  </form>
Enter fullscreen mode Exit fullscreen mode
function submitStep(){
    activeStepIndex.value++;
}
Enter fullscreen mode Exit fullscreen mode

Feature 3
Now we want to put some heading or design to Every step or Some hidden input field or if we wish to modify design of footer or submit button then we can implement via header and footer slot.

MultiStepForm.vue

  <form @submit.prevent="submitStep">
    <slot name="header" />

    <div 
     v-for="(step, index) in steps" 
     :key="index" 
     :id="'step'+(index+1)" 
     v-show="activeStepIndex===index"
     class="vue-form-step">

       <slot :name="'step'+(index+1)" />

     </div>

    <slot name="footer">
      <button type="submit">Next</button>
    </slot>
  </form>
Enter fullscreen mode Exit fullscreen mode

Now here we have provided default html to footer slot, Means if we doesn't mention footer slot in parent component, submit button will be available by default else we need to implement it in parent.

LoginPage.vue

<template>
  <MultiStepForm :steps="steps">
    <template #header>
      <h1>Get Started -></h1>
    </template>
    <template v-slot:footer>
      <button class="btn btn-primary" 
       type="submit">Get OTP</button>
    </template>
    //steps slots
  </MultiStepForm>
</template>
Enter fullscreen mode Exit fullscreen mode

Feature 4
Suppose if you have a long form and you want to skip some particular step based dynamically on some logic then we can do that by providing some data inside 'steps' props

LoginPage.vue

const steps= reactive([
    {'step_no':1,'step_valid':false,'step_skip':false},
    {'step_no':2,'step_valid':false,'step_skip':true},
    {'step_no':3,'step_valid':false,'step_skip':false},
]);
Enter fullscreen mode Exit fullscreen mode

Also now we have prepared the data we need to implement the logic to skip the step. Also we need to take care we don't do any action if it's the final step.

MultiStepForm.vue

const emit = defineEmits(['onComplete']);
function submitStep(){
  if(activeStepIndex.value === props.steps.length){
    //final step
    emit('onComplete');
    return false;
  }
  activeStepIndex.value++;
  while(props.steps[activeStepIndex.value].step_skip === true){
    activeStepIndex.value++;
  }
}
Enter fullscreen mode Exit fullscreen mode

Feature 5
Looks like we have implemented most of the feature required for multi step form, Now let's take things to next level where we want to check if current step is valid. And if it is invalid then we show some form error and restrict user to go to the next step.
Remember we have used 'step_valid' inside step object, let's use it.

MultiStepForm.vue

const emit = defineEmits(['onComplete','valdiateStep']);
function submitStep(){
  if(!props.steps[activeStepIndex.value].step_valid){
    emit('valdiateStep', stepIndex);
    return false;
  }
  //increase activeStepIndex
}
defineExpose({ submitStep });
Enter fullscreen mode Exit fullscreen mode

LoginPage.vue

<template>
  <MultiStepForm
  ref="multiStepForm"
  :steps="steps"
  @onComplete="submitForm"
  @validateStep="validateStep">
   //slots
  </MultiStepForm>
</template>
Enter fullscreen mode Exit fullscreen mode
let multiStepForm = ref(null);
function validateStep(stepIndex){
  //run validation of step
  //if step is valid then
  steps[stepIndex].step_valid=true;
  multiStepForm.value.submitStep();
  //else show errors
}
function submitForm(){
  //api call to submit all data via post request
  //redirect to somewhere
}
Enter fullscreen mode Exit fullscreen mode

If you are facing issues while implementing validation library then we have simple validation package to fit your needs have a look.
https://www.npmjs.com/package/vue-nice-validate

Feature 6
Now coming onto our last step is to submit form via old way i.e.
via html action and method if action prop is provided from parent.
And here's how our MultiStepForm component will look like

MultiStepForm.vue

<form :id="id" :action="action" :method="method">
Enter fullscreen mode Exit fullscreen mode
const props = defineProps(['steps','id','action','method']);
Enter fullscreen mode Exit fullscreen mode
function submitStep(){
  if(!props.steps[activeStepIndex.value].step_valid){
    emit('valdiateStep', stepIndex);
    return false;
  }
  let isLastStep = (activeStepIndex.value === props.steps.length);
  if(isLastStep && props.action){
    submitForm();
    return true;
  }
  if(isLastStep){
    emit('onComplete');
    return true;
  }
  activeStepIndex.value++;
  while(props.steps[activeStepIndex.value].step_skip===true){
    activeStepIndex.value++;
  }
}
function submitForm(){
  document.getElementById(props.id).submit();
}
Enter fullscreen mode Exit fullscreen mode

I hope you enjoyed reading this.
Please give your love and support to my Github open source repositories.
And If you are stuck somewhere or need to outsource your project feel free to hire us.

Thank You and Have Nice Day!

Top comments (0)