by Matt Garrish
Identifying the role is the easy part, though. Just as standard form controls have states and properties that are controlled by the reading system, so too must you add and maintain these on any custom controls you create.
A state, to clarify, tells you something about the nature of the control at a given point in time: if an element represents a checkable item, for example, its current state will either be checked or unchecked; if it can be hidden, its state may be either hidden or visible; if it’s collapsible, it could be expanded or collapsed; and so on.
Properties, on the other hand, typically provide meta information about the control: how to find its label, how to find a description for it, its position in a set, etc.
States and properties are both expressed in ARIA using attributes. For example, the list of available states currently includes all of the following:
aria-busy
aria-checked
aria-disabled
aria-expanded
aria-grabbed
aria-hidden
aria-invalid
aria-pressed
aria-selected
The list of available properties is much larger, but a small sampling includes:
aria-activedescendant
aria-controls
aria-describedby
aria-dropeffect
aria-flowto
aria-labelledby
aria-live
aria-posinset
aria-required
Note
See section 6.6 of the ARIA specification for a complete list of all states and properties, including definitions.
All of these state and property attributes are supported in EPUB 3 content documents, and their proper application and updating as your controls are interacted with is how the needed information gets relayed back to the reader. (Note: you only have to maintain their values; you don’t have to worry about the underlying process that identifies the change and passes it on.)
The natural question at this point is which states and properties do you have to set when creating a custom control. It would be great if there were a simple chart that could be followed, but unfortunately the ones that you apply is very much dependent on the type of control you’re creating, and what you’re using it to do. To be fully accessible, you need to consider all the ways in which a reader will be interacting with your control, and how the states and properties need to be modified to reflect the reality of the control as each action is performed. There is no one-size-fits-all solution, in other words.
Note
To see which properties and states are supported by the type of control you’re creating, refer to the role definitions in the specification. Knowing what you can apply is helpful in narrowing down what you need to apply.
If you don’t set the states and properties, or set them incorrectly, it follows that you’ll impair the ability of the reader to access your content. Implementing them badly can be just as frustrating for a reader as not implementing them at all, too. You could, for example, leave the reader unable to start your audio clip, unable to stop it, stuck with volume controls that only go louder or softer, etc. Their only recourse will be shutting down their ebook and starting over.
These are the accessibility pitfalls you have to be aware of when you roll your own solutions. Some will be obvious, like a button failing to initiate playback, but others will be more subtle and not caught without extensive testing, which is also why you should engage the accessibility community in checking your content.
But let’s take a look at some of the possible issues involved in maintaining states. Have a look at the following much-reduced example of list items used to control volume:
- Louder
- Softer
This setup looks simple, as it omits any states or properties at the outset, but now let’s consider it in the context of a real-world usage scenario. As the reader increases the volume, you’ll naturally be checking whether the peak has been reached in order to disable the control. With a standard button, when the reader reached the maximum volume you’d just set the button to be disabled with a line of JavaScript; the button gets grayed out for readers and is marked as disabled for the accessibility API. Nice and simple.
List items can’t be natively disabled, however (it just doesn’t make any sense, since they aren’t expected to be active in the first place). You instead have to set the aria-disabled attribute on the list item to identify the change to the accessibility API, remove the event that calls the JavaScript (as anyone could still activate and fire the referenced code if you don’t), and give sighted readers a visual effect to indicate that the button is no longer active.
Likewise, when the reader decreases the volume from the max setting, you need to re-enable the control, re-add the onclick event, and re-style the option as active. The same scenario plays out when the reader hits the bottom of the range for the volume decrease button.
In other words, instead of having to focus only on the logic of your application, you now also have to focus on all the interactions with your custom controls. This extra programming burden is why rolling your own was not recommended at the outset. This is a simple example, too. The more controls you add, the more complex the process becomes and the more potential side-effects you have to consider and account for.
If you still want to pursue your own controls, though, or just want to learn more, the Illinois Center for Information Technology and Web Accessibility maintains a comprehensive set of examples, with working code, that are worth the time to review. You’ll discover much more from their many examples than I could reproduce here. The ARIA authoring practices guide also walks through the process of creating an accessible control.
A quick note on tabindex is also in order, as you no doubt noticed it on the preceding examples. Although this is actually an HTML attribute, it goes hand-in-hand with ARIA and custom controls because it allows you to specify additional elements that can receive keyboard focus, as well the order in which all elements are traversed (i.e., it improves keyboard accessibility). It is critical that you add the attribute to your custom controls, otherwise readers won’t be able to navigate to them.
Note
What elements a reader can access by the keyboard by default is reading system-dependent, but typically only links, form elements, and multimedia and other interactive elements receive focus by default. Keep this in mind when you roll your own controls, otherwise readers may not have access to them.
Here’s another look at our earlier image button again:
By adding the attribute with the value 0, we’ve enabled direct keyboard access to this img element. The 0 value indicates that we aren’t giving this control any special significance within the document, which is the default for all elements that can be natively tabbed to. To create a tab order, we could assign incrementing positive integers to the controls, but be aware that this can affect the navigation of your document, as all elements with a positive tabindex value are traversed before those set to 0 or not specified at all (in other words, don’t add the value 1 because to you it’s the first element in your control set).
In many situations, too, a single control would not be made directly accessible. The element that contains all the controls would be the accessible element, as in the following example:
Access to the individual controls inside the grouping div would be script-enabled. This would allow the reader to quickly skip past the control set if they aren’t interested in what it does (otherwise they would have to tab through every control inside it).
Note
See the HTML5 specification for more information on how this attribute works.
&nbs
p; A last note for this section concerns event handlers. Events are what are used to trigger script actions (onclick, onblur, etc.). How you wire up your events can impact on the ability of the reader to access your controls, and can lead to keyboard traps (i.e., the inability to leave the control), so you need to pay attention to how you add them.
We could add an onclick event to our image button to start playback as follows:
But, if we’d accidentally forgotten the tabindex attribute, a reader navigating by keyboard would not have been able to find or access this control. Even though onclick is considered a device-independent event, if the reader cannot reach the element they cannot use the Enter key to activate it, effectively hiding the functionality from them.
You should always ensure that actions can be triggered in a device-independent manner, even if that means repeating your script call in more than one event type. Don’t rely on any of your readers using a mouse, for example.
But again, it pays to engage people who can test your content in real-world scenarios to help discover these issues than to hope you’ve thought of everything.
Forms
Having covered how to create custom controls, we’ll now turn to forms, which are another common problem area ARIA helps address. To repeat myself for a moment, though, the first best practice when creating forms is to always use the native form elements that HTML5 provides. See the last section again for why rolling your own is not a good idea.
When it comes to implementing forms, the logical ordering of elements is one key to simplifying access and comprehension. The use of tabindex can help to correct navigation, as we just covered, but it’s better to ensure your form is logically navigable in the first place. Group form fields and their labels together when you can, or place them immediately next to each other so that one always follows the other in the reading order.
And always clearly identify the purpose of form fields using the label element. You should also always add the new HTML5 for attribute so that the labels can be located regardless of how the reader enters the field or where they are located in the document markup. This attribute identifies the id of the form element the label element labels:
I’ve also added the aria-labelledby attribute to the input element in this example to ensure maximum compatibility across systems, but its use is critical if your form field is not identified by a label element (only label takes the for attribute). As the label element can be used in just about every element that can carry a label, there’s little good reason to omit using it.
For example, if you have to use a table to lay out your form, don’t be lazy and use table cells alone to convey meaning: