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!
Archived Comments
Great article. Exactly what I was looking for. Thanks!
Glad it helped. :)
Yea, me too. Thanks for the lesson Raymond.
Definitly the easiest way I've found to create a multistep form ! Thank you
You are most welcome.
Thank you very much!
You are most welcome.
Simple and great, thank you
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?
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. :)
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!
Please note I consider myself *very* new to Vue and still have a lot to learn.
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..
Nope, but in theory it would work well with any of the other Vue validation plugins.