
Multi-Parts Forms
A long-term client asked us one day if we could create an online form with multiple parts for a section on their website. There are a lot of solutions already available so it was trivial to select one that met their needs. But it did get me thinking about how difficult if I were to create my own solution.
Let’s start with some goals so we have something to shoot for:
- No dependencies; the solution to be easy to include into existing projects.
- Progressive Enhancement; the underlying form should still work without JavaScript. Of the available solutions that I browsed, none worked without JavaScript.
- User-Friendly; ease the experience as much as possible for the user.
- Accessible; screen readers should also be able to use the form.
- Works with HTML 5 form validation; it should not interrupt or muck up HTML 5 form validation.
See it in action
It’s still a work-in-progress but I am pleased with how it’s turning out.
The JavaScript
Without JavaScript, the form simply displays in its entirety. This is obviously desired because I don’t want to lock people out if they don’t have JavaScript enabled.
Converting a form into a Multi-Part Form (or mpform
) requires a few lines of JavaScript:
const my_form = document.querySelector( `#my-form` );
const my_mpf = new MultiPartForm( my_form );
my_mpf.setup(); // Should I rename the function to init()?
The constructor can accept a config object:
new MultiPartForm( my_form, {
nav_panel_selector: ".mpform-nav-panel", // CSS Selector for the Navigation Panel.
progress_panel_selector: ".mpform-progress-panel", // CSS Selector for the Progress Panel.
step_selector: ".mpform__step" // CSS Selector that identifies the individual steps or parts in the form.
} );
The Navigation Panel can be included with an empty <div>
. You’ll probably want to include it otherwise people won’t be able to navigate between the parts.
<div class="mpform-nav-panel"></div>
<!-- class name can be changed using the config object in the constructor. -->
You can include the optional Progress Panel in a similar fashion.
<div class="mpform-progress-panel"></div>

The CSS
The only CSS included is for the animation that plays when navigating Navigating between parts. It’s completely optional so you could just forgo it and create your own styles. But the default animation does allow some configuration via CSS Custom Properties (aka CSS variables).
.mpform-anim-to-show {
animation-name: var( --mpform-anim-to-show-name, mpform-anim-to-show );
animation-duration: var( --mpform-anim-to-show-duration, 1s );
animation-fill-mode: var( --mpform-anim-to-show-fill-mode, forwards );
}
Being User-Friendly
Resetting the form will jump back to step 1. Submitting the form with errors will jump to the step containing the first error.
Being Accessible
The navigation buttons and progress bar have all been given appropriate aria-label
attributes.

Just one more thing…
Setting an ARIA Live Region isn’t as straight-forward as I’d hoped. Given that navigating between steps effectively creates dynamic content, a live region is the encouraged choice. My initial thought was to slap the role
attribute on the <form>
element but then I remembered that forms have an implicit ARIA role of, well, form. And while I’m not sure I think the implicit role would take precedence anyway. There’s also the advice that we shouldn’t specify redundant roles1. Heck, there’s even a handy table that says <form>
elements should only use the roles of none, presentation or search.
This means I will need to create a viewport of sorts that functions as the live region to display the current part or step. Not impossible but just more work than I anticipated.
Final thoughts
Once I fix the ARIA live region issue I’ll probably release the code on GitHub. I’m not sure if anyone will find it useful but 🤷♀️
Is there another way?
My solutions allows users to navigate freely between the form parts i.e. they are not required to complete part 1 before moving onto part 2. This is by design though I can see the benefit of the alternative. I suppose the whole point of breaking a (presumably long) form into smaller parts is to help guide the user to enter valid data while hiding additional form inputs before they are needed. My solution could be modified to require valid data before moving on; call the Constraint Validation API’s checkValidity()
function whenever the user wants to navigate to another step.
What about using <details>
?
If all we want to do is avoid overwhelming the user with too many form inputs, why not just use the <details>
element? It certainly would be a lot easier and is already well supported.
Notes
- While digging deeper into the
role
attribute, I learned that it can actually accept multiple values via a space-separated string. However, this does not apply multiple roles to the element as the browser will select the first appropriate role.
Social Media Links