Multi-step forms

Designing multi-step forms

It’s best practice to divide a long form into a stepped series of smaller forms with validation at each step. Multi-step forms can be less daunting and easier to understand and correct than a long form. Multi-step forms can be designed across pages, or on the same page.

Follow these best practices for multi-step forms:

  • Split the form up according to its logical groups of controls (e.g., contact information and questionnaire).
  • Validate the current step before exposing the next.
  • Label optional steps and enable users to skip them.
  • Ensure keyboard focus moves smoothly from step to step, backwards and forwards. At a new step, set the focus preferably on the next relevant form element, or relevant heading or container (a heading or container would need the tabindex="-1" attribute to programmatically receive focus).
  • Avoid a time limit to fill out the form. If a limit is required, enable the user to adjust or extend the time limit.
  • If the steps are across pages, repeat overall instructions on every page.
  • Indicate progress in the form (“Step x of y”).

Indicating progress in a multi-step form

Each step should note the user’s progress. Approaches 1 and 2 apply only to multi-page forms.

Approach 1: Using the page title

Add a note indicating progress (e.g., “Step 2 of 4:”) to the beginning of the <title> element, before the name of the step or any error notification. The <title> element is the first thing declared on page load to screen reader users and so provides immediate feedback.

HTML

<title>Step 2 of 4: Contact information – Survey X – Company Z</title>

Approach 2: Using the main heading

Add a note indicating progress (e.g., “(Step 2 of 4)” to the end of the page’s main heading, making the information prominent and easily discoverable by visual scan.

HTML

<h1>Contact information (Step 2 of 4)</h1>

Approach 3: Using the HTML5 progress element

You can use a HTML5 <progress> element to inform users of their progress, as the first element in each step.

Survey (Step 1 of 7)

HTML

Survey <progress max="7" value="1">(Step 1 of 7)</progress>

Some operating systems animate the <progress> element, which violates WCAG’s Success Criterion 2.2.2 Pause, Stop, Hide. The animation can be stopped by using custom styling with browser-specific CSS as shown below:

CSS

/* Microsoft IE */ 
progress {  
   color: #036;
}
/* Apple Safari and Google Chrome */ 
progress::-webkit-progress-bar {
   background-color: #036;
}
/* Mozilla Firefox */ 
progress::-moz-progress-bar {
   background-color: #036;
}

Approach 4: Using a step-by-step indicator

A step-by-step indicator can help users orient themselves in a stepped process. This example uses an ordered list with each step being a list item. Visually hidden text, off screen but still encountered by assistive tech, indicates the current and completed status of steps (the WET class .wb-inv visually hides the text). Previous steps are linked so users can review them. Any data already entered in the current step should be saved.

  1. Completed: Billing Address
  2. Current: Shipping Address
  3. Review Order
  4. Payment
  5. Finish Purchase

HTML

<div class="tlwrapper">
   <ol class="timeline">
      <li class="timeline-past">
         <span class="wb-inv">Completed: </span>
         <a href="billing.html">Billing Address</a>
      </li>
      <li class="timeline-current">
         <span class="wb-inv">Current: </span>
         <span>Shipping Address</span>
      </li>
      <li><span>Review Order</span></li>
      <li><span>Payment</span></li>
      <li><span>Finish Purchase</span></li>
   </ol>
</div>
View CSS
.wb-inv {
   clip: rect(1px,1px,1px,1px);
   height: 1px;
   margin: 0;
   overflow: hidden;
   position: absolute;
   width: 1px;
}
.box-content {
   font-size: .7em;
   overflow: auto;
   padding: .5em;
}
.tlwrapper {
   display: table;
   width: 100%;
}
.timeline {
   display: table-row;
   counter-reset: timeline;
}
.timeline li.timeline-past {
   background-color: #ccc;
}
.timeline li:first-child {
   padding-left: 0;
}
.timeline li {
   display: table-cell;
   width: 20%;
   counter-increment: timeline;
   list-style: none;
   text-align: center;
   padding: .25em .5em;
   overflow: hidden;
   position: relative;
   background-color: #fff;
   padding-left: 25px;
   white-space: nowrap;
}
.timeline li.timeline-current > span, 
.timeline li.timeline-current a {
   color: #036;
   font-weight: bold;
}
.timeline li a, .timeline li > span {
   z-index: 100;
   position: relative;
   display: block;
   color: #555;
}
.timeline li.timeline-current > span:before {
   color: #fff;
   background-color: #036;
   border-color: #036;
}
.timeline li a:before, 
.timeline li > span:before {
   display: inline-block;
   color: #555;
   content: counter(timeline);
   background-color: transparent;
   border: 3px solid #555;
   margin-right: .25em;
   border-radius: 5px;
   padding: 0 .25em;
}
.timeline li:after {
   left: 0;
   top: 50%;
   border: solid transparent;
   content: " ";
   height: 0;
   width: 0;
   position: absolute;
   pointer-events: none;
   border-color: rgba(151, 204, 237, 0);
   border-left-color: #ccc;
   border-width: 20px;
   margin-top: -20px;
}

Alternative approach: Using aria-current="step"

This alternative replaces the visually-hidden text "Current" with an aria-current="step" attribute applied to the parent <li> element. Screen readers declare "current step" in addition to the list item text. User agent support for aria-current when applied to a non-focusable element like <li> (as opposed to controls) is an open question. Visually-hidden text, on the other hand, is universally supported.

HTML

[…]
   <li class="timeline-current" aria-current="step">
      <span>Shipping Address</span>
   </li>
[…]

  Good example: The WET steps form

The WET steps form (example) is a same-page multi-step form. The Steps form documentation describes the input markup requirements.

The WET steps form does not yet support optional steps with a “skip” button.

You define each step within a <fieldset> element and name it with a nested <legend> element. You place the step's form elements in a <div> following the <legend>.

HTML

<fieldset>
   <legend>Contact Information</legend>
   <div>(Form elements go here, hidden when inactive.)</div>
</fieldset>
<fieldset>
   <legend>Questionnaire</legend>
   <div>(Form elements go here, hidden when inactive.)</div>
</fieldset>

JavaScript hides the <div> when the step is unselected. JavaScript also adds navigation buttons “next” and “previous”, which trigger validation on click.


The HTML markup for Approaches 3 and 4 is from the Web Accessibility Initiative (WAI) document: Multi-Page Forms in the Forms Concepts (WAI) tutorial of the Web Accessibility Tutorials. Eric Eggert and Shadi Abou-Zahra. Copyright © 2019 W3C® (MIT, ERCIM, Keio). Updated 27 July 2019 (first published September 2014).

Related WCAG resources

Related WCAG resources

Success criteria

Techniques

Back to top