Accessibility - WAI-ARIA basics II

Accessibility - WAI-ARIA basics II

Information drawn from

Enhancing keyboard accessibility

As discussed in a few other places in the module, one of the key strengths of HTML with respect to accessibility is the built-in keyboard accessibility of features such as buttons, form controls, and links. Generally, you are able to use the tab key to move between controls, the Enter/Return key to select or activate controls, and occasionally other controls as needed (for example the up and down cursor to move between options in a <select> box).

However, sometimes you will end up having to write code that either uses non-semantic elements as buttons (or other types of control), or uses focusable controls for not quite the right purpose. You might be trying to fix some bad code you’ve inherited, or you might be building some kind of complex widget that requires it.

In terms of making non-focusable code focusable, WAI-ARIA extends the tabindex attribute with some new values:

We discussed this in more detail and showed a typical implementation back in our HTML accessibility article — see Building keyboard accessibility back in.

Accessibility of non-semantic controls

This follows on from the previous section — when a series of nested <div>s along with CSS/JavaScript is used to create a complex UI-feature, or a native control is greatly enhanced/changed via JavaScript, not only can keyboard accessibility suffer, but screenreader users will find it difficult to work out what the feature does if there are no semantics or other clues. In such situations, ARIA can help to provide those missing semantics.

Form validation and error alerts

First of all, let’s revisit the form example we first looked at in our CSS and JavaScript accessibility article (read Keeping it unobtrusive for a full recap). At the end of this section we showed that we have included some ARIA attributes on the error message box that displays any validation errors when you try to submit the form:

<div class="errors" role="alert" aria-relevant="all">

We could go further with our ARIA usage, and provide some more validation help. How about indicating whether fields are required in the first place, and what range the age should be?

<p>Fields marked with an asterisk (*) are required.</p>
<input type="text" name="name" id="name" aria-required="true">
<input type="number" name="age" id="age" aria-required="true">

Note: You can see the finished example live at form-validation-updated.html.

WAI-ARIA also enables some advanced form labelling techniques, beyond the classic <label> element. We already talked about using the aria-label property to provide a label where we don’t want the label to be visible to sighted users (see the Signposts/Landmarks section, above). There are some other labelling techniques that use other properties such as aria-labelledby if you want to designate a non-<label> element as a label or label multiple form inputs with the same label, and aria-describedby, if you want to associate other information with a form input and have it read out as well. See WebAIM’s Advanced Form Labeling article for more details.

There are many other useful properties and states too, for indicating the status of form elements. For example, aria-disabled=”true” can be used to indicate that a form field is disabled. Many browsers will just skip past disabled form fields, and they won’t even be read out by screenreaders, but in some cases they will be perceived, so it is a good idea to include this attribute to let the screenreader know that a disabled input is in fact disabled.

If the disabled state of an input is likely to change, then it is also a good idea to indicate when it happens, and what the result is. For example, in our form-validation-checkbox-disabled.html demo there is a checkbox that when checked, enables another form input to allow further information be entered. We’ve set up a hidden live region:

<p class="hidden-alert" aria-live="assertive"></p>

which is hidden from view using absolute positioning. When this is checked/unchecked, we update the text inside the hidden live region to tell screenreader users what the result of checking this checkbox is, as well as updating the aria-disabled state, and some visual indicators too:

function toggleMusician(bool) {
  let instruItem = formItems[formItems.length-1];
  if(bool) {
    instruItem.input.disabled = false; = '#000';
    instruItem.input.setAttribute('aria-disabled', 'false');
    hiddenAlert.textContent = 'Instruments played field now enabled; use it to tell us what you play.';
  } else {
    instruItem.input.disabled = true; = '#999';
    instruItem.input.setAttribute('aria-disabled', 'true');
    hiddenAlert.textContent = 'Instruments played field now disabled.';

Describing non-semantic buttons as buttons

A few times in this course already, we’ve mentioned the native accessibility of (and the accessibility issues behind using other elements to fake) buttons, links, or form elements (see UI controls in the HTML accessibility article, and Enhancing keyboard accessibility, above). Basically, you can add keyboard accessibility back in without too much trouble in many cases, using tabindex and a bit of JavaScript.

But what about screenreaders? They still won’t see the elements as buttons. If we test our fake-div-buttons.html example in a screenreader, our fake buttons will be reported using phrases like “Click me!, group”, which is obviously confusing.

We can fix this using a WAI-ARIA role. Make a local copy of fake-div-buttons.html, and add role=”button” to each button <div>, for example:

<div data-message="This is from the first button" tabindex="0" role="button">Click me!</div>

Now when you try this using a screenreader, you’ll have buttons be reported using phrases like “Click me!, button” — much better.

Note: Don’t forget however that using the correct semantic element where possible is always better. If you want to create a button, and can use a

Guiding users through complex widgets

There are a whole host of other roles that can identify non-semantic element structures as common UI features that go beyond what’s available in standard HTML, for example combobox, slider, tabpanel, tree. You can see a number of useful examples in the Deque university code library, to give you an idea of how such controls can be made accessible.

Let’s go through an example of our own. We’ll return to our simple absolutely-positioned tabbed interface (see Hiding things in our CSS and JavaScript accessibility article), which you can find at Tabbed info box example (see source code).

This example as-is works fine in terms of keyboard accessibility — you can happily tab between the different tabs and select them to show the tab contents. It is also fairly accessible too — you can scroll through the content and use the headings to navigate, even if you can’t see what is happening on screen. It is however not that obvious what the content is — a screenreader currently reports the content as a list of links, and some content with three headings. It doesn’t give you any idea of what the relationship is between the content. Giving the user more clues as to the structure of the content is always good.

To improve things, we’ve created a new version of the example called aria-tabbed-info-box.html (see it running live). We’ve updated the structure of the tabbed interface like so:

<ul role="tablist">
  <li class="active" role="tab" aria-selected="true" aria-setsize="3" aria-posinset="1" tabindex="0">Tab 1</li>
  <li role="tab" aria-selected="false" aria-setsize="3" aria-posinset="2" tabindex="0">Tab 2</li>
  <li role="tab" aria-selected="false" aria-setsize="3" aria-posinset="3" tabindex="0">Tab 3</li>
<div class="panels">
  <article class="active-panel" role="tabpanel" aria-hidden="false">
  <article role="tabpanel" aria-hidden="true">
  <article role="tabpanel" aria-hidden="true">

Note: The most striking change here is that we’ve removed the links that were originally present in the example, and just used the list items as the tabs — this was done because it makes things less confusing for screenreader users (the links don’t really take you anywhere; they just change the view), and it allows the setsize/position in set features to work better — when these were put on the links, the browser kept reporting “1 of 1” all the time, not “1 of 3”, “2 of 3”, etc.

The new features are as follows:

In our tests, this new structure did serve to improve things overall. The tabs are now recognized as tabs (e.g. “tab” is spoken by the screenreader), the selected tab is indicated by “selected” being read out with the tab name, and the screenreader also tells you which tab number you are currently on. In addition, because of the aria-hidden settings (only the non-hidden tab ever has aria-hidden=”false” set), the non-hidden content is the only one you can navigate down to, meaning the selected content is easier to find.

Note: If there is anything you explicitly don’t want screen readers to read out, you can give them the aria-hidden=”true” attribute.

Test your skills! You’ve reached the end of this article, but can you remember the most important information? You can find some further tests to verify that you’ve retained this information before you move on — see Test your skills: WAI-ARIA.


This article has by no means covered all that’s available in WAI-ARIA, but it should have given you enough information to understand how to use it, and know some of the most common patterns you will encounter that require it.


Last update on 09 Feb 2022