A Multi-Step Form in Vue.js

This post is more than 2 years old.

Last week I wrote up a demo explaining how to build a simple quiz using Vue.js. As part of that process, I demonstrated how to render one question of the quiz at a time and navigate through the set of questions. It occurred to me that it may make sense to also demonstrate how to build a simple "multi-step" form in Vue.js as well. Let's begin with a simple example. I'll show the HTML first.

<div id="app">
  <form>
  <div v-if="step === 1">

    <h1>Step One</h1>
    <p>
    <legend for="name">Your Name:</legend>
    <input id="name" name="name" v-model="registration.name">
    </p>

    <p>
    <legend for="email">Your Email:</legend>
    <input id="email" name="email" type="email" v-model="registration.email">
    </p>

    <button @click.prevent="next()">Next</button>
    
  </div>

  <div v-if="step === 2">
    <h1>Step Two</h1>
    <p>
    <legend for="street">Your Street:</legend>
    <input id="street" name="street" v-model="registration.street">
    </p>

    <p>
    <legend for="city">Your City:</legend>
    <input id="city" name="city" v-model="registration.city">
    </p>

    <p>
    <legend for="state">Your State:</legend>
    <input id="state" name="state" v-model="registration.state">
    </p>

    <button @click.prevent="prev()">Previous</button>
    <button @click.prevent="next()">Next</button>

  </div>

  <div v-if="step === 3">
    <h1>Step Three</h1>
    
    <p>
    <legend for="numtickets">Number of Tickets:</legend>
    <input id="numtickets" name="numtickets" type="number" v-model="registration.numtickets">
    </p>

    <p>
    <legend for="shirtsize">Shirt Size:</legend>
    <select id="shirtsize" name="shirtsize" v-model="registration.shirtsize">
      <option value="S">Small</option>
      <option value="M">Medium</option>
      <option value="L">Large</option>
      <option value="XL">X-Large</option>
    </select>
    </p>

    <button @click.prevent="prev()">Previous</button>
    <button @click.prevent="submit()">Save</button>
    
  </div>
  </form>

  <br/><br/>Debug: {{registration}}
</div>

This is a three step form that kind of mimics a typical conference registration. I've got three steps each set up in a div using v-if to control if the particular step is rendered. Note the buttons in each step. I call either next, prev, or submit based on what part of the process I'm in. The last part (where it says Debug) was simply a little, well, "debugger" so I could see data being entered as the process was completed.

Now let's look at the code.

const app = new Vue({
  el:'#app',
  data() {
    return {
      step:1,
      registration:{
        name:null,
        email:null,
        street:null,
        city:null,
        state:null,
        numtickets:0,
        shirtsize:'XL'
      }
    }
  },
  methods:{
    prev() {
      this.step--;
    },
    next() {
      this.step++;
    },
    submit() {
      alert('Submit to blah and show blah and etc.');      
    }
  }
});

I've got two interesting things going on here. First, I decided to put all the form data in a key called registration. This gave me a nice separation between the "UI stuff" and the actual form data. The prev and next methods simply change the step value. In theory I could add additional logic here to ensure I don't go to an invalid step, but as I'm writing the HTML myself I trusted myself to not screw that up. Finally, the submit action would do a simple AJAX post and then either redirect to a thank you page or show a server-side error. (I assumed folks were here for the multi-step form and not the AJAX, but if you want to see that, just ask!) Here is a complete embed of this demo:

See the Pen Multi-step Vue form by Raymond Camden (@cfjedimaster) on CodePen.

You may be curious - what if you didn't want to use AJAX for the form submission. I mean, there's a real form there, right? Well don't forget that v-if actually takes things in and out of the DOM. If you tried to submit this version, only the form fields from the last visible step would be posted. Here's a modified version that uses v-show instead:


<div id="app">
	<form action="https://postman-echo.com/post" method="post">
	
	<div v-show="step === 1">

		<h1>Step One</h1>
		<p>
		<legend for="name">Your Name:</legend>
		<input id="name" name="name" v-model="registration.name">
		</p>

		<p>
		<legend for="email">Your Email:</legend>
		<input id="email" name="email" type="email" v-model="registration.email">
		</p>

		<button @click.prevent="next()">Next</button>

	</div>

	<div v-show="step === 2">
		<h1>Step Two</h1>
		<p>
		<legend for="street">Your Street:</legend>
		<input id="street" name="street" v-model="registration.street">
		</p>

		<p>
		<legend for="city">Your City:</legend>
		<input id="city" name="city" v-model="registration.city">
		</p>

		<p>
		<legend for="state">Your State:</legend>
		<input id="state" name="state" v-model="registration.state">
		</p>

		<button @click.prevent="prev()">Previous</button>
		<button @click.prevent="next()">Next</button>

	</div>

	<div v-show="step === 3">
	<h1>Step Three</h1>

	<p>
	<legend for="numtickets">Number of Tickets:</legend>
	<input id="numtickets" name="numtickets" type="number" v-model="registration.numtickets">
	</p>

	<p>
	<legend for="shirtsize">Shirt Size:</legend>
	<select id="shirtsize" name="shirtsize" v-model="registration.shirtsize">
		<option value="S">Small</option>
		<option value="M">Medium</option>
		<option value="L">Large</option>
		<option value="XL">X-Large</option>
	</select>
	</p>

	<button @click.prevent="prev()">Previous</button>
	<input type="submit" value="Save">

	</div>
	</form>

	<br/><br/>Debug: {{registration}}
</div>

Note I also added an action and method to the form tag. In this case I'm using the Postman echo service so the result won't be terribly pretty, but you will see all the fields posted. Here's this version:

See the Pen Multi-step Vue form (2) by Raymond Camden (@cfjedimaster) on CodePen.

Woot. Ok, so for a final version, I thought I'd try a quick Veutify version. Veutify is a material design UI skin for Vue and it's pretty bad ass. I want to be clear that I whipped this up pretty quickly so it is probably not the most ideal version, but it looks pretty cool.

See the Pen Multi-step form, Vuetify by Raymond Camden (@cfjedimaster) on CodePen.

In case you're curious, that control is called the stepper and has quite a few options for how to configure it. The default is a horizontal step process, but I noticed the labels in the header went away when the width was constrained. This version seemed to work better in the space I have here.

Anyway - I hope this helps, and as always, remember there's going to be many different ways of doing what I demonstrated here. If you've been working with Vue and have some examples of this, please share below!

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Brad King posted on 3/10/2018 at 1:43 PM

Great article. Exactly what I was looking for. Thanks!

Comment 2 (In reply to #1) by Raymond Camden posted on 3/10/2018 at 1:52 PM

Glad it helped. :)

Comment 3 (In reply to #1) by Abdo Arbab posted on 3/21/2018 at 12:38 PM

Yea, me too. Thanks for the lesson Raymond.

Comment 4 by tamalou malodo posted on 5/23/2018 at 12:03 PM

Definitly the easiest way I've found to create a multistep form ! Thank you

Comment 5 (In reply to #4) by Raymond Camden posted on 5/23/2018 at 2:29 PM

You are most welcome.

Comment 6 by AlexeyKot posted on 9/13/2018 at 9:24 AM

Thank you very much!

Comment 7 (In reply to #6) by Raymond Camden posted on 9/13/2018 at 1:15 PM

You are most welcome.

Comment 8 by resky adi nugraha posted on 9/27/2018 at 3:12 AM

Simple and great, thank you

Comment 9 by T posted on 10/11/2018 at 9:01 PM

I'm working on a massive multi-part form and I was aiming to have each step be a component, so I wasn't working with one huge page. However, I really like how you have the form data obj in one place. Something about passing the entire object into a component as a prop and emitting it back out again seems wrong. Any recommendations here?

Comment 10 (In reply to #9) by Raymond Camden posted on 10/11/2018 at 9:16 PM

Well, you could use your approach and pass the 'big ob' back and forth, with each component only "writing" to part of ob, like formdata[page1].name = ray. Maybe? Kinda guessing here. :)

Comment 11 (In reply to #10) by Tim posted on 10/11/2018 at 9:37 PM

Thanks for your reply! Hey if my approach didn't immediately throw red flags for you, then maybe it's not so bad - it's working, so maybe that's good enough. Thanks again - also your article/tutorial is great!

Comment 12 (In reply to #11) by Raymond Camden posted on 10/12/2018 at 1:50 PM

Please note I consider myself *very* new to Vue and still have a lot to learn.

Comment 13 by Peter Tichelaar posted on 3/13/2019 at 7:19 PM

Hi Raymond what a superb and simple wizard form! Question. Did you ever considered field validation in your wizard form? In the situation of a wizard form the fields must be validated before going to the next tab..

Comment 14 (In reply to #13) by Raymond Camden posted on 3/13/2019 at 8:53 PM

Nope, but in theory it would work well with any of the other Vue validation plugins.