Forms are seemingly one of the simplest things to implement on a website, but are your forms accessible to everyone? In this article, I start with a 3-step sign-up form and discuss the rationale behind the accessibility techniques I used for it.
Before you read on…
To benefit the most from this article, you’ll need a screen reader. It’s important to test with screen readers during development. If you use a Mac, like myself, you can turn on VoiceOver, which will work best with Safari. If you’re on Windows, you can use a free screen reader called NVDA, which works best with Firefox. The ChromeVox Extension is also a decent screen reader to use if you’re a Chrome user, though a January 2014 WebAIM Survey showed a low percentage of screen reader users using the Chrome browser.
Recently, the focus around creating a more accessible web has grown, and with good reason - there has been an increase in litigation surrounding web accessibility and few web developers really know how to implement it. Personally, I also think about how much I benefit from the web. I feel something of a responsibility to make the projects I work on as accessible to everyone as I can so others might benefit, as well.
Many developers feel that making sites accessible is difficult or very time-consuming and it’s understandable because, like anything else web-related, it comes with its’ own vocabulary and learning curve. Once you have that knowledge, though, implementing accessibility can be a much easier task. Let’s take a look at a sign-up form for an imaginary fitness web app, and walk through some of the things that I did to make it accessible.
Example Registration Form
I’ve put together a demo of an accessible registration form.
Rather than giving an example of some super-simple form, I wanted to show something that we might come across often - a multi-step form. I also wanted to show that you can still have good-looking (well, I tried), smooth interfaces without having to compromise due to accessibility. Try using the form with a keyboard, and then with a screen reader.
A Label For Every Input
One of the easiest steps we can take toward more accessible forms is making sure that each of our inputs has a related label. Assistive technologies, such as screen readers, use labels to announce the purpose of inputs to users, but only if they have a way of knowing that an input has a label. While this is a simple technique, it’s often overlooked.
The most elementary and widely used method is assigning an
id to an input and
<label> element, using that
id as the value of the
attribute on the label. We can also simply nest our input and label text within
<label> element. Labeling inputs using one of these two methods has an
additional benefit in that a user can also click the label text to focus the
field. This can be helpful for users who may have a motor impairment but are
still using a mouse, giving them more area to click.
<!-- label linked to the input using the input id and the for attribute on the label --> <input id="userGoalWeightLoss" name="userGoal" type="radio" value="lose"> <label for="userGoalWeightLoss">Lose Weight</label> <!-- or nesting the label text and input within the label element --> <label><input name="userGoal" type="radio" value="lose"> Lose Weight</label>
The WAI-ARIA specification also provides us with two other methods of labeling
elements for assistive technologies. The first is the
attribute, which accepts a space-separated list of element
ids - elements to
use as labels. The elements that are referenced can be completely hidden
display: none), but will still be used as labels.
The second method is the
aria-label attribute, which we can use to provide a
label for the element, directly, instead of relying on another element. This is
primarily meant for situations where the label isn’t even in the DOM, for one
reason or another. Whenever possible, It’s probably better to use a visual
element for labeling, rather than relying on this type of hidden label, to
provide a better experience for users who aren’t necessarily using assistive
<!-- aria-labelledby is used to link other elements for use as a label --> <legend id="userFitnessGoalLabel">Fitness Goal</legend> <ul role="radiogroup" aria-labelledby="userFitnessGoalLabel"> ... </ul> <!-- aria-label can be used to provide an inline label --> <ul role="radiogroup" aria-label="Fitness Goal"> ... </ul>
Progress indicators are relatively common on multi-step forms. Think of any time you’ve gone through a rather complex sign-up process or anytime you’ve ordered a product online. It’s a great way to communicate to the user where they are and how much farther they have to go.
While it’s easy to give sighted users a good experience with this, I also wanted to give screen reader users the same experience. Here is the starting code for the progress indicator.
<ol tabindex="0"> <li>Account Setup</li> <li>Goal & Body Profile</li> <li>Complete</li> </ol>
To make sure that a screen reader user will see this progress, I’ve added
tabindex="0" so it appears in the tab order. This will cause a screen reader
to read all of its contents when the user tabs to (or clicks) it. At this point,
when I tab to the steps, VoiceOver with Safari says “1 Account Setup, 2 Goal &
Body Profile, 3 Complete, group”. While this is a good starting point, the user
doesn’t really have the information they probably want to know - the current
step that they’re on.
If we look back at the WAI-ARIA spec, we’ll see that we can define something as
a progress bar widget. The spec gives us attributes to tell assistive
technologies that this element shows progress with
the minimum and maximum values are with
respectively, and what the current value is with
By default, screen readers will speak the value of
aria-valuenow as a
percentage, based on the minimum and maximum attributes. The spec also gives us
a way to tell assistive technologies to present the value in a different format
aria-valuetext. Here is the new code for our progress indicator with the
WAI-ARIA progress bar role and attributes.
<ol tabindex="0" role="progressbar" aria-valuemin="1" aria-valuemax="3" aria-valuenow="1" aria-valuetext="Step 1 of 3: Account Setup"> <li>Account Setup</li> <li>Goal & Body Profile</li> <li>Complete</li> </ol>
To finalize the progress indicator, I added
aria-hidden="true" to the list
items so ChromeVox wouldn’t read them all before announcing the WAI-ARIA
information. Also, to make sure the user doesn’t lose the focus outline, I put a
border around the current step when the progress indicator is focused. To me,
this seemed better than having a border around the entire progress indicator.
Validation Error Reporting
What is a form these days without some front-end validation? For the most part, the way that we report validation errors to a user doesn’t have to change. In order to make our errors more accessible, we just need to supplement our normal validation reporting.
I actually got a little hung up on trying to implement accessible error
reporting because, while I was testing, it became apparent that browsers and
assistive technologies don’t really follow the WAI-ARIA standard when it comes
alert role is meant to communicate important
information to the user, such as errors. When an element with the
appears, the browser is supposed to trigger an event in the accessibility API,
which assistive technologies are listening for so they can notify the user.
Usually, when assistive technologies become aware of an alert, they will stop
what they’re doing (perhaps reading a paragraph) and notify the user of the
content of the alert.
Initially, I tried to put the
alert role on the validation errors that get
shown beneath the input fields, but it simply didn’t work correctly. As of this
writing, it appears that the most (not totally) reliable way to get alerts read
is to provide an empty element with
role="alert", and update its contents
later on with any errors that may come up. It’s a pretty hacky solution. Here is
an example of what I’m talking about.
<div class="is-visually-hidden" role="alert"></div>
So, we have this empty
<div> with the
alert role. Whenever I need to
validate the form, I empty the
<div> and insert paragraphs containing the
errors. The new contents of the
<div> will trigger an alert event in the
accessible API (with most browsers) and get announced to the user (by most
<!-- attempting to advance the form without entering information... --> <div class="is-visually-hidden" role="alert"> <p>Please enter your email address.</p> <p>Please enter a password.</p> <p>Please confirm your password.</p> </div>
It doesn’t stop at merely alerting the user to validation errors, though. Sighted users are able to tell which validation error goes to which input based on the location. A screen reader user who is just tabbing through the form won’t come across those error messages, though, so it can be frustrating because they’re not hearing what the error was for each field as they tab through them.
To fix this, we can use the
aria-describedby attribute, which takes a
space-separated list of element
ids (just like
aria-labelledby) - elements
to use as a description. Descriptions have lower priority than other information
that assistive technologies announce about elements, such as the label, or the
type of element. Descriptions are generally read last.
In my case, I gave each visual validation error a randomly generated
in the same code that marks the input as invalid (for the red border), I also
aria-describedby attribute to the randomly generated
id. Now, when
assistive technologies come across the field, it will read that error message as
a description (some call it a hint).
<legend class="form-group-title" id="userEmailLabel">Email Address</legend> <div class="form-group-inputs"> <input class="is-invalid" name="userEmail" type="text" aria-labelledby="userEmailLabel" aria-describedby="validation-error-113555"> </div> <p class="validation-error" id="validation-error-113555">Please enter an email address.</p>
To round out our validation, we should provide a way for assistive technologies
to announce whether or not a field is invalid. Once again, the WAI-ARIA spec has
us covered with the
aria-invalid attribute. By setting
on the email address field, VoiceOver with Safari says “Email Address, invalid
data, edit text”.
<legend class="form-group-title" id="userEmailLabel">Email Address</legend> <div class="form-group-inputs"> <input class="is-invalid" name="userEmail" type="text" aria-labelledby="userEmailLabel" aria-describedby="validation-error-113555" aria-invalid="true"> </div> <p class="validation-error" id="validation-error-113555">Please enter an email address.</p>
Hopefully you’re able to take at least one thing away from this article. Label all the inputs! This is such an easy and common thing that gets overlooked. Create the same experience for all users and be sure that everyone can get the same information, such as their progress through a form or validation error messages.
It’s our responsibility as developers to make the web a better place for everyone. Making websites accessible does increase the complexity a bit, but having some knowledge on the matter makes it easier to take on a seemingly daunting task.