When I do an audit of a web-based system against WCAG, I subject each test unit to both automated & manual tests against nearly 250 Best Practices. Of those, 40 of them relate directly to forms. The #1 way which users interact with web-based systems is through the use of forms. Some sites, such as Facebook & Twitter are nothing more than forms and content generated by users interacting with those forms. Naturally it makes sense to ensure that the forms are both usable and accessible
High-Level Form Accessibility Requirements
The list below is a high-level outline of what I test for in those 40 Best Practices. I’ve placed these in an ordered list not to indicate importance but rather so I can refer to them by number later.
- The form as a whole should (if necessary) provide instructions for successful submission of the form
- All form elements must have explicit labels.
- The labels must be clear and informative with respect to what type of information is being asked for
- Any special constraints for each form element (i.e. format of the input, etc.) must be clearly disclosed
- Validation messages must be clear to allow effective recovery from errors
- In addition to the above, there are two important accessibility concerns that are not really unique to forms but should be a concern no matter what type of content you’re working with.
- All interactive functionality should work via keyboard
- Focus should change as needed based on interaction while, at the same time, not changing in unexpected ways.
I’d like to focus the rest of this blog post on labels, instructions, and errors (2, 4, and 5, above).
Labeling Basics
I don’t want to spend too much time belaboring the point about explicit labels. This is a topic that has been handled rather well by others and I’m not inclined to reinvent the wheel here. If form labeling is completely new to you, please check out Creating Accessible Forms by WebAIM. At a high level here’s what you should be doing:
<label for="test">Date: </label>
<input type="text" name="test" id="test" />
The rendered input looks like this:
In the code sample above, we see the use of the LABEL
element which wraps the word ‘Date’. The 'for'
attribute on the LABEL
has the same value as the 'id'
attribute on the INPUT
element. This indicates that this LABEL
is for the specified INPUT
. This is very simple & easy to do and each and every form element should be labeled in this way. JAWS will say “Date colon edit. Type in text”.
The dirty little secret here is that most recent versions of JAWS would read that fine without the explicit label markup. The problem is that the way this happens is unpredictable even among different form element types. For instance, JAWS will not read the adjacent text for an unlabeled SELECT
element. In fact, it will not even read text within a LABEL
adjacent to a SELECT
element without that explicit association from the 'for'
attribute. Doing it right eliminates this risk. Now we’re adhering to item #2 in our list.
It doesn’t take long to get tricky
What if you needed the date formatted to adhere to a specific format?
Maybe you’d code that form element like this:
<label for="test">Date: </label>
<input type="text" name="test" id="test" /> must be mm/dd/yyyy format
What will JAWS read? It will still only say “Date colon edit. Type in text.” Why? Because when JAWS enters forms mode, it will only read the form elements, their values/ potential values and their related labels. In short, the other text is ignored. In this case, even though on-page text discloses the formatting instructions, you are not adhering to #4 in our list.
Wait! What about WAI-ARIA?!?
If you’re already clued into accessibility and you’ve read this far, chances are you’re saying “well, DUH use ARIA“, to which I’d reply “Of course!”.
The problem is so many ARIA examples suck and miss the point.
Here are some bad examples:
I’m not going to say where I’ve seen these, in order to protect the guilty, but these examples come directly from tutorials on ARIA.
<div>
<label for="test">Date: </label>
<input type="text" name="test" id="test" aria-labelledby="instructs" />
<span id="instructs">must be mm/dd/yyyy format</span>
This form input has an explicit label element and is also labeled with the aria-labelledby
attribute. The LABEL
element will not be read by JAWS when in forms mode because the value in aria-labelledby
actually overrides the label. JAWS only says “Must be mm slash dd slash yyyy format edit” Why? Because we’ve pointed to a different label.
The cool thing about aria-labelledby
is that it can take a space-delimited list of ID values. So, one workaround might be to do this:
<span id="dlabel">Date: </span>
<input type="text" name="test" id="test" aria-labelledby="dlabel instructs" />
<span id="instructs">must be mm/dd/yyyy format</span>
Unfortunately, you’re now in a situation where the only people who are able to benefit from this approach are people with assistive technologies which support WAI-ARIA such as JAWS 10+, Window-Eyes 7+, etc.. What we know from experience is that users of assistive technologies tend not to upgrade very fast. When I worked for SSB, I once had a call from a person who used JAWS 4 asking for an updated script to something. The current JAWS version at that time was JAWS 9 and JAWS 10 was in Beta. So, long story short, the method above is not backwards compatible and will probably leave some people high and dry.
Another misguided approach would be to use a LABEL
and aria-labelledby
:
<label for="test" id="dlabel">Date: </label>
<input type="text" name="test" id="test" aria-labelledby="dlabel instructs" />
<span id="instructs">must be mm/dd/yyyy format</span>
The test form INPUT
has its LABEL
element back, this time using the ID
value used in the SPAN
and is labelled with the aria-labelledby
attribute. This form also reads as intended (again, reading “Date colon must be mm slash dd slash yyyy format edit”) but it requires the extraneous addition of the 'id'
attribute on the LABEL
. This is extra unnecessary work, IMO. Also, there’s the fact that aria-labelledby
is intended to identify the element (or elements) that labels the current element. In this case, the date formatting information is instructions, not a proper label. So, although this approach allows assistive technology to read the information, this is the wrong way to do it.
Use aria-describedby For Instructions
Per the WAI-ARIA spec., A label provides the user with the essence of what the object does, whereas a description is intended to provide additional detail that some users might need. This is where aria-describedby
comes in and can help us properly mark up our date formatting instructions.
<label for="test">Date: </label>
<input type="text" name="test" id="test" aria-describedby="instructs" />
<span id="instructs">must be mm/dd/yyyy format</span>
This form INPUT
has its LABEL
element properly associated. The additional instructions are provided with the aria-describedby
attribute. This is the preferred method of labeling and disclosing constraints. This uses the LABEL
element properly and links to the formatting instructions via the aria-describedby
attribute. JAWS says “Date colon edit. Must be mm slash dd slash yyyy format” (note the placement of the word ‘edit’ in this case indicating that “Date:” is the actual label. )
Accessible Error Messages
Turns out aria-describedby
is perfect for accessible error handling as well.
While a lot of my customers tend to do a pretty good job at labeling form elements, one thing that remains elusive is effective error messaging. First, focus needs to shift directly to the first error message after validation occurs. Second, the error messages themselves need to be accessible. Often, developers will provide specific messages adjacent to or above the form elements in error, as indicated by the image below.
Such a thing might be marked up like this:
<div class="errors">Error: Please enter a valid date.</div>
<div>
<label for="test">Date: </label>
<input type="text" name="test" id="test" aria-describedby="instructs" />
<span id="instructs">must be mm/dd/yyyy format</span>
</div>
The problem, thanks to forms mode, is that the error message may not be read. With forms mode on in JAWS, all that would be read is “Date colon edit. Must be mm slash dd slash yyyy format edit”. Fortunately, the repair for this is rather simple:
<div id="error" class="errors">Error: Please enter a valid date.</div>
<div>
<label for="test">Date: </label>
<input type="text" name="test" id="test" aria-describedby="error instructs" />
<span id="instructs">must be mm/dd/yyyy format</span>
</div>
As is the case with aria-labelledby
, the aria-describedby
attribute takes a space-delimited list of strings which indicate the element IDs for the passages of text which serve to describe this element. JAWS says “Date colon edit. Error colon Please enter a valid date. Must be mm slash dd slash yyyy format edit”. In other words, it reads the LABEL
then reads each of the identified elements in the order in which they appear. Now we have accessibly labeled our form element and provided clear instructions regarding format constraints and provided accessible error messages.
Occam’s Razor Cuts Deep
All that WAI-ARIA stuff is neat, isn’t it? I appreciate greatly the ability to provide additional semantics that will allow web authors the ability to denote relationships between content. But is it really necessary? In some cases, such as things like tree menus and complex grid controls, sure. In this case? not at all. Remember when I said …because when JAWS enters forms mode, it will only read the form elements, their values/ potential values and their related labels.? Let’s get back to basics for a moment:
<label for="test">
<div class="errors">Error: Please enter a valid date.</div>
Date:
<input type="text" name="test" id="test" />
<span>must be mm/dd/yyyy format</span>
</label>
Without any ARIA, the example above works in all assistive technologies, including legacy assistive technologies and assistive technologies that have spotty (or zero) support for WAI-ARIA. In fact, the DIV
wrapper could be removed from around the field as the LABEL
now serves that purpose, thus reducing DIVitis.
Finally, indicating required fields
The broadly accepted paradigm across the web is usually to indicate that a field is required through the provision of an asterisk (“*”) next to the label. This asterisk character may not be read by text-to-speech assistive technologies, depending on the specific assistive technology and users’ settings. Even in a best case scenario, the word “star” would be announced. Savvy users may intuit that this means the field is required. But really, why should they? Why not state, explicitly, that the field is required?
Indicating required-ness with text
It really doesn’t get any easier or obvious than simply adding the word “required” right there in the label.
<label for="test">
Date (required):
<input type="text" name="test" id="test" />
<span>must be mm/dd/yyyy format</span>
</label>
Indicating required-ness with hidden text
If the design staff have a heart attack when you suggest the above, one approach might be to use off-screen text for the required text. In this case, you simply uss CSS to position that content off screen.
<label for="test">
Date * <span class="offScrn">required</span>:
<input type="text" name="test" id="test" />
<span>must be mm/dd/yyyy format</span>
</label>
Indicating required-ness in the DOM
HTML5 adds the ability to indicate required-ness right there in the element itself using the ‘required’ attribute. This gets announced by text-to-speech assistive technologies, provided the user’s not on a legacy browser and/ or legacy assistive technology.
<label for="test">
Date *:
<input type="text" name="test" id="test" required="required" />
<span>must be mm/dd/yyyy format</span>
</label>
ARIA bridges the gap
Interestingly, more assistive technologies & browsers support ‘aria-required’ than those which support the new ‘required’ attribute. One sure-fire way of indicating required-ness is to combine them.
<label for="test">
Date *:
<input type="text" name="test" id="test" required="required" aria-required="true" />
<span>must be mm/dd/yyyy format</span>
</label>
The Holy Grail
The following example puts everything together to demonstrate the ideal placement of error messages, required-ness, and special instructions:
<label for="test">
<div class="errors">Error: Please enter a valid date.</div>
Date (required):
<input type="text" name="test" id="test" required="required" aria-required="true" />
<span>must be mm/dd/yyyy format</span>
</label>
The case of legacy assistive technologies
This post was updated on June 4, 2013. In the original post from October 2011, I said:
When it comes to users of older assistive technologies which were released before WAI-ARIA even existed, my response is to say “Sorry, please upgrade”. While I recognize and understand that assistive technology users are somewhat slower to upgrade, I don’t think its too unreasonable to expect that people keep up a little. As I mentioned earlier, ARIA support has existed in JAWS since version 10 (November 2008). I understand that assistive technologies are expensive. I understand that other assistive technologies are not doing a good job at ARIA support. I don’t think these are valid arguments for not using the best available tools for making accessible websites.
Unfortunately there’s a huge difference between idealism and realism. We should be able to use WAI-ARIA and have it work across all systems. We should be able to rely on assistive technology vendors to update their products to support the latest standards.
We should be able to count on users to be their own advocates to pressure their assistive technology vendors to meet their needs. Those things aren’t happening and, as a developer, I need to also need to consider my duty to the user and provide all users the best experience possible. Using the LABEL
as a wrapper and including all relevant content in it does work for all users of all assistive technologies. So, use that.