Building an accessible website: How to create accessible forms
Forms are an integral part of many websites but they pose unique challenges for accessible websites.
Forms are one of the most engaging parts of your website. They are used for collecting data and interacting directly with your audience. Most importantly, forms allow your site to support e-commerce. Naturally you would want to expose your forms to as many users as possible, and not exclude any particular group of users from accessing these revenue generating areas of your website.
HTML 5 is already well equipped to handle accessibility and, in fact, screen readers are tuned to take advantage of what is already available. Let's go over the basic starting points for any form structure.
Forms consist of different types of input. Each input or input group on your form should have an accompanying label. Simply formatted like so:
<label for="name">Name</label> <input id="name">
That tight little package is very accessible as long as the label "for" attribute is linked to the respective input's "id" attribute.
Modern site design sometimes eliminates these labels in favour of using the "placeholder" attribute. This will fail the Web Content Accessibility Guidelines (WCAG) as the "placeholder" is only sporadically supported across the many operating systems, browsers, and screen reader combinations. Something else to consider is that most browsers will display the placeholder text within the input field as fairly light text which is bound to fail the WCAG contrast minimum. Browsers support the use of "aria" attributes to assist with accessibility. In this case, an "aria-label" can be used to complement the placeholder attribute like so:
<input id="name" placeholder="Enter your name" aria-label="Enter your name">
It's important to note that the above is not an ideal solution because, in some cases, the screen reader will read out both the placeholder and aria-label attributes. This method should only be used when space is at an absolute premium.
It is common for a website to include entry instructions for a field. For instance, a date field can have formatting help:
<label for="date">Date</label> <input id="date" /><span>(year/month/day)</span>
The above example shines a light on one of the pratfalls of forms and accessibility - how users with accessibility challenges interact with forms. Many of these users employ the keyboard as the sole means of navigating websites. This is done using the tab keys and down and up arrow keys to read through your content. The arrow keys fairly reliably allow the user to access all of the content with the keyboard. When it comes to forms, however, the arrow keys will not suffice as you can't arrow out of a text input, textarea, or list, and therefore become trapped. For this reason, users limited to the keyboard use tab keys to navigate forms. There are two problems with the above code:
- The instructions appear after the field
- The instructions are not tab accessible.
Here we can use the "aria-label" attribute to give more help to the user:
<label for="date">Date</label> <input id="date" aria-label="Enter date as year slash month slash day"><span>(year/month/day)</span>
Note the use of "slash" in the aria-label to ensure that the screen reader is announcing accurately.
That's fine for help specifically tied to a particular field, but in some cases the help is more general but no less important:
<form> ... <div>Fill out the form and click submit only once</div> ... </form>
The above will be completely missed due to the user tabbing through the document. We can use the tabindex attribute to force the keyboard to stop at this element and thus be read by the screen reader:
<form> ... <div tabindex="0">Fill out the form and click submit only once</div> ... </form>
Notice that we have set the tabindex to 0. This forces the respective element to fall into the tab order based on the flow of the page, not on some arbitrary ordering by the developer. Elements that are tab stops by default (i.e. form input elements) have a tabindex of 0, so these elements will mingle gracefully and fall in order as they are presented in the document flow with elements to which you assign a tabindex of 0. In any case, arbitrary tab indexing is difficult to maintain - accessibility issues notwithstanding.
Now that we are thinking of the order of things, make sure that all of your instructions are placed ahead of where they are needed. Let's look at our sample again with a simple submit button added:
<form> ... <input type="submit" value="Submit form"> <div tabindex="0">Fill out the form and submit only once</div> ... </form>
See the problem? The user is getting the instruction after they need it. Switch it up:
<form> ... <div tabindex="0">Fill out the form and submit only once</div> <input type="submit" value="Submit form"> ... </form>
Form validation and error handling
Validating form input is a critical part of data collection. Unfortunately this happens to be the most neglected area when it comes to accessibility - mainly because of the reliance on visual cues.
Let's start with the basic task of informing users that a field is mandatory. The two most common practices are the use of an asterisk and/or colour to distinguish mandatory fields. These are usually accompanied by a comment:
- * Indicates required field, or
- Red fields are mandatory
Obviously a comment like "Red fields are mandatory" is completely useless to a user who is blind or colour-blind. Also, some screen readers ignore the asterisk and therefore that cue will be lost as well.
Let's go back to our original example and make our field mandatory in the traditional sense:
<label for="name" style="color: red;">Name *</label> <input id="name">
There are two ways to effectively mark fields as required.
- The HTML 5 "required" attribute
- The "aria-required" attribute
These are implemented as such:
<label for="name" style="color: red;">Name *</label> <input id="name" required aria-required="true">
Note we have left in the older style colour and asterisk clues so as to not leave out those not using a screen reader. Also note that we have used both the HTML 5 and aria-style required attributes. The HTML 5 attribute may be missed by some screen readers, but is included to take advantage of HTML 5's validation mechanism. Fortunately, the screen reader will not duplicate the required message when both attributes are used. Note that "aria-required" must include a value. There is no need to mark optional fields aria-required="false".
<div id="error_message" aria-live="polite">Your error message here</div>
When the above div's contents are updated, the screen reader will announce your message. In some cases it may not be practical to change the form's behaviour in such a fundamental way. Another solution is to error check the form on submission, but take care to direct the user to your error field if the submission fails.
Here is an example of this approach:
Typically, disabled elements are fine as long as they are not buttons. These elements should have aria-disabled="true" included in their attributes. It is quite common that forms use a passive system to ensure all required fields are completed correctly. The submit button will remain disabled until the user completes all of the required fields. This type of interface will most likely be lost on users relying on screen readers, or users who have trouble distinguishing colour. It is better to use a more direct solution as discussed in the error trapping section above.
Is your website accessible to people with disabilities?
Get our beginner's guide to website accessibilityDownload your FREE copy
More complex challenges
Some types of elements can pose greater challenges. In many cases, your form may invoke a plugin such as a date picker. If this is something of your own design, you can attend to the date picker itself to make it accessible. However, if it is based on code that you do not control, and that code is not accessible, you need to provide an alternative way for users to enter a date. For example, if you have a date picker that is triggered by landing on a date field, allow users to enter the date manually and message them as such including any applicable instructions:
<input id="birth_date" type="text" aria-label="Enter the date as year slash month slash day" onfocus="showDatePicker();">
In some cases you might have information that will supplement the label for an input, as in this case:
The graphic above depicts a form with a check-box that has the properly associated label element. However, in this case, it was felt that the text above the label was also important to the first field. We don't want to eliminate the label, but rather augment it with this additional information. This can be accomplished by using the "aria-describedby" attribute which the screen reader will read as well as the label.
<div class="row" id="bus-arrival-description"> Off-street bus drop off/pickup is available. </div> <div class="row"> <input aria-describedby="bus-arrival-description" type="checkbox" id="bus-arrival" name="bus-arrival" value="Yes"> <label for="bus-arrival">I am arriving by bus</label> </div> <div class="row"> <label for="bus-number">Number of buses:</label><input type="text" id="bus-number" name="bus-number" value=""> </div>
The aria-describedby can be referred to by several labels to further clarify what users are inputting. In the following example, we have several check-boxes for grade level, each with a label - K, 1, 2, 3, etc.
If we leave it up to the labels, the screen reader will be reading out 1,2,3, etc. - not very helpful. One solution would be to make each label Grade 1, Grade 2, etc., but you can see that this would result in a cluttered interface. By using the same aria-describedby for each input, we can have the screen reader announce Grade level 1, Grade level 2, etc., while leaving the interface as designed:
<label id="group-grades">Grade Level:</label> <input aria-describedby="group-grades" role="checkbox" type="checkbox" id="pre-k" value="Pre-K"> <label id="pre-k-check" for="pre-k" class="age-groups">Pre-K</label> <input aria-describedby="group-grades" role="checkbox" type="checkbox" id="k" value="K"> <label id="k-check" for="k" class="age-groups">K</label> <input aria-describedby="group-grades" role="checkbox" type="checkbox" id="grade-1" value="1"> <label id="grade-1-check" for="grade-1" class="age-groups">1</label> ... <input aria-describedby="group-grades" role="checkbox" type="checkbox" id="grade-12" value="12"> <label id="grade-12-check" for="grade-12" class="age-groups">12</label>