Understanding Accessibility: Forms

Published on

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.

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

Field label

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 providing a <label> element, using that id as the value of the for attribute on the label. We can also simply nest our input and label text within the <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 aria-labelledby 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 technologies.

<!-- 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>

Showing Progress

3-Step progress indicator

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 &amp; 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 role="progressbar", what the minimum and maximum values are with aria-valuemin and aria-valuemax, respectively, and what the current value is with aria-valuenow.

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 with 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 &amp; Body Profile</li>
  <li>Complete</li>
</ol>

Now, VoiceOver with Safari says “Step 1 of 3: Account Setup, 0 percent, progress indicator”. This is telling the user just what they need to know - how many steps there are in the process, as well as the current step that they’re on. I’ve also written a bit of JavaScript (StepProgressBar.js) that updates these values as the user moves through the form.

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

Validation error

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 to role="alert". The alert role is meant to communicate important information to the user, such as errors. When an element with the alert role 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 screen readers).

<!-- 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 id, and in the same code that marks the input as invalid (for the red border), I also set the 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 aria-invalid="true" 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>

Takeaways

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.

Also, be sure to test with different platforms, browsers, and assistive technologies when you can. Like I pointed out earlier, sometimes things don’t work the way they should, or the way you would expect. We might implement something for accessibility according to the specification, but it may not actually help users because the technologies currently in use don’t function properly (just like CSS or JavaScript and cross-browser compatibility).

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.


Verify the signed markdown version of this article:

curl https://keybase.io/sethlopez/key.asc | gpg --import && \
curl https://sethlopez.me/article/understanding-accessibility-forms/ | gpg