1. Web Design
  2. JavaScript

Build a Multi-Step Form Interface

Scroll to top
Read Time: 14 min

Form usability is an incredibly important topic in web design. As one of the primary input interfaces provided to users, the usability of a form is essential to a good user experience.

Today, we're going to build a multi-part form, complete with validation and animation. We'll cover a lot of ground, so buckle up!

Form High-level Best Practices

Form interface design is a minefiled of usability obstacles. Before we start, let's talk about a few form best practices.

Don't make your users think too hard

Forms are generally not the place to encourage unique interaction. Good forms provide obvious navigation techniques and full transparency. Good forms are well-labeled and easy to navigate.

Don't get too fancy

It's important not to deviate too far from default form behaviors. There are standards that are the basis for the default behaviors of forms. Deviation from these standards may have negative effects on accesibility, so consider retaining those default styles when possible.

Remember, the user doesn't have to stay

The user of your form doesn't have to stay. They choose to stay. Filling out a form is a chore, so make it easy. (If possible, make it fun!) Don't confuse or demand from the user; instead, establish a dialogue around the questions the form is asking. Be polite.

When to use multi-section techniques

Multi-section forms are certainly a good technique, sometimes. There's not a single answer for "how many inputs does it take before I should split the form up". Instead, always consider whether splitting the form into separate sections will help or hinder usability. Secondarily, consider whether it helps or hinders other aspects of the interaction design.

If you have forms with very distinct sections, it may be worth separating into multiple parts. Checkout processes are a common example of this. (Personal info, shipping info, payment info, and confirmation are very distinct and generally substantial sections.)

Planning the Interaction

We're going to create a signup form with arbitrary fields. We need to know what section we are currently on, so we'll need an indicator at the top of the form. We want to transition our form sections horizontally, sliding from right to left. To accomplish this, we will set the different sections to have absolute positioning inside a section "window" element. We also want to have two buttons; one is the normal submit button. The other is a "next section" button.

The Markup

Here's our form:

The markup is fairly straightforward, but let's talk about a few pieces of it.

  • Fieldsets: Fieldsets are a semantic element for grouping inputs; this suits our situation perfectly.
  • Classnames: jQuery Validate uses classes to define built-in types. We'll see how this works in a minute.
  • The fields are arbitrary. We've wrapped the radio inputs in a paragraph tag for easier formatting.
  • Submit and next buttons: the anchor tag with a class of button will be used to go to the next section. The submit input will show when appropriate via JavaScript.

The Styles (LESS-Flavored)

This is long, get ready..

Let's walk through the important part of the styles.


The form itself is set to a specific width, centered with margin: 0 auto, then set to position:relative. This positioning allows for absolute positioning of child elements to place them absolutely, relative to the containing form. The containing form holds three primary types of elements: the section tabs and the fieldset "window", as well as the buttons.

The section tabs are placed relative to the containing element, and "pulled" up with a negative top margin. We compensate for the effect on the rest of the layout with an equal margin bottom.

The fieldset "window" is set to be positioned absolute, relative to the parent form element. The width and height are both set to 100%. The purpose of this window is to hold elements, then hide them when they fall outside the edges with overflow: hidden. We couldn't do this on the form, because we want to retain the indicator tabs.

Finally, the button elements (an anchor tag and a submit input) are styled to be positioned absolute to the bottom right corner of the form, offset by 20 pixels from the bottom and right. We also have added a simple CSS transition to the button elements to darken the background on hover.

A Few More Notes

  • Fieldsets are set to be position: absolute. We have two classes for two states, and a default state. The default state pulls the fieldset off to the left of the form; the .current class puts the fieldset in the visible area of the form, and finally the .next class pushes the fieldset off to the right of the form.
  • The current class has an opacity of 1; the default (and inherently the .next) states have opacity of 0.
  • We animate the fieldsets between these classes with simple css transitions.
  • The .error and .valid classes will help with the validation styling. We're using Merriweather Sans, a font freely available for use via Google Webfonts.

The JavaScript

Here's the part that makes all of the interaction work. A few notes before we look at the code: this code depends on jQuery and jQuery Validate. jQuery validate is a plugin that has been around for a very long time, and therefore has been tested and proven by thousands of people.

Our basic strategy: set up some validation rules for the form, including a custom function to check for a telephone number. We want users to be able to navigate back through previously finished sections. We want them to be able to use the enter button to move to the next section; however, we don't want to allow users to advance to subsequent sections until previous sections have been completed and are valid.

If the user tries to click on a tab for a section beyond the ones they have completed, we want to avoid navigating to that section.

We want to rely on classes (rather than jQuery animations) to transition between states.

So, with these things in mind, here is the final JavaScript. (Note: if you aren't up on your jQuery, this may be a bit daunting; however, stick with it anyway, and you'll learn by diving into code.) After the full script, we will take it one piece at a time and explain what's going on.

So, let's go piece by piece.

This function is the setup function for jQuery Validate. First, we are telling the plugin to take the signup form and apply validation to it. If validation succeeds, we add a class of valid to the label that the validation plugin inserts after the input element, and replace the text with the utf-8 checkmark .

We are also registering the error callback, although we aren't actually doing anything in this function. Not registering the function seems to have the same callback as the success function on error. We are setting the onsubmit hook to false; this is because pressing enter automatically submits the form, which by default triggers validation to prevent invalid form submission. Preventing the default behavior of form submit doesn't prevent the form validation. The result is that the fields on the "next" screen already show validation errors, despite the form never being submitted.

This function listens to the keyup event, triggered on the form. If the key that was hit was enter (key code 13), we then perform the following check. If the next button is still visible and the current section is valid, prevent the default behavior of the enter key being pressed while on a form, which is form submission.

We then call nextSection(), which advances the form to the next section. If the current section contains any invalid inputs, those inputs are identified and the form does not advance. If the next button is not visible, that means we are on the final section (which we will see in subsequent functions) and we want to allow the default behavior (form submission) to occur.

These functions are straightforward. If you press the button with the id of "next", we want to advance to the next section. Remember the nextSection() function contains all necessary checks for form validation.

On the submit event on the form, we want to avoid form submission if the next button is visible or if the current fieldset isn't the last one.

The goToSection() function is the workhorse behind the navigation of this form. It takes a single argument - the index of the target navigation. The function takes all fieldsets with an index greater than the passed in index parameter and removes the current class, and adds a next class. This pushes the elements to the right of the form.

Next, we remove the current class from all fieldsets with an index less than the passed in index. Next, we choose the list item equal to the passed in index and add a current class.

Subsequently we remove the current class from all other sibling lis. We set a timeout, after which we remove the next class and add the current and active classes to the fieldset with an index equal to the passed in index parameter.

The active class lets us know that the user can navigate to this particular section again. If the passed in index parameter is 3 (the final fieldset), we hide the next button and show the submit button. Otherwise, we want to make sure the next button is visible and the submit button is hidden. This allows us to hide the submit button unless the last fieldset is visible.

The nextSection() function is basically a wrapper around the goToSection() function. When nextSection is called, a simple check is made to be sure that we are not on the last section. As long as we are not, we go to the section with an index that is equal to the index of the current section, plus one.

We are listening to the click event on the list items. This function checks to make sure the list item has the active class, which it receives once the user initially arrives to that section of the form by completing all previous sections. If the list item does have the class, we call goToSection and pass in the index of that list item. If the user clicks on a list item corresponding to a section they can't access yet, the browser alerts them to let them know that they have to complete the previous sections before advancing.

Finally, we are adding a method specified by the jQuery Validate plugin that manually checks an input. We won't spend much time on this, because this exact functionality can be found in the Validate documentation.

Essentially, we check to make sure the text the user put into the phone field matches the regex (the long string of numbers and symbols). If it does, then the input is valid. If it doesn't, the message "Please specify a valid phone number" is added after the input. You could use this same functionality to check for any kind of input (it doesn't have to use a regex).

Note: Do not use this as a password authentication method. It is very unsafe and anyone can view the source to see the password.


We've used semantic HTML and simple LESS combined with some minimal JavaScript to build a robust form interaction. The methods used to build this form, especially using classnames to identify state and to define functionality and animations, can be used in almost any interactive project. The same functionality could be used for demonstration step-throughs, games, and anything else that depends on simple state-based interactions.

What else would you do to this form? What types of interactions have you found to help users more naturally flow through a tedious process such as filling out a long multi-step form? Share them in the comments!

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.