Understanding ARIA Widgets

Written by Todd Kloots

ARIA is a W3C specification that can be used to dramatically improve the accessibility of custom widgets, especially for users of screen readers. For those unfamiliar with ARIA this article aims to explain the unfamiliar through the lens of the familiar: existing HTML widgets.

The goal is to train developers how to think about ARIA widgets such that using ARIA feels familiar and natural—something all developers can feel confident using and making part of their everyday toolkit.

In the interest of being as concise and straightforward as possible, without being overly pedantic this article will be limited in scope to the markup and styling for ARIA widgets. Keyboard access for ARIA widgets, while an important topic, has been intentionally left out since it is a broader topic deserving an article of its own, and the implementation guidance varies between devices (mobile and desktop) and platforms (Mac and Windows).

Widget Roles

ARIA provides additional semantics beyond what is available in current implementations of HTML. As the spec indicates, it’s a bridging technology—filling gaps between versions of the HTML specs. For example, neither HTML 4 or 5 provide a toolbar or dialog widget, but both can be declared using ARIA. Consider the following two examples:

Toolbar Example

<div id="tb-1" role="toolbar">
  <button type="button">Print</button>
  <button type="button">Move</button>
  <button type="button">Delete</button>
</div>

Dialog Example

<div role="dialog"></div>

As illustrated in the examples, ARIA widgets are declared by applying the role attribute (part of HTML5) to existing elements. ARIA roles can be declared in the markup, or applied via JavaScript:

// Using native DOM
document.getElementById("tb-1").setAttribute("role", "toolbar");

// Using jQuery
$("#tb-1").attr("role", "toolbar");

// Using YUI
Y.one("#tb-1").set("role", "toolbar");

Like the <input> element’s type attribute, the ARIA role attribute can only be set to one of several predefined values. In the interest of Progressive Enhancement, when building custom widgets always start with the closest native semantics and supplement with ARIA. Consider this tree widget that uses a <ul> as its foundation:

<ul role="tree" tabindex="0">
  <li role="treeitem" tabindex="-1">Item 1</li>
  <li role="presentation">
    <a href="#group-1" role="treeitem" aria-expanded="true" tabindex="-1">Item 2</a>
    <ul id="group-1" role="group">
      <li role="treeitem" tabindex="-1">Item 2-1</li>
      <li role="treeitem" tabindex="-1">Item 2-2</li>
      <li role="treeitem" tabindex="-1">Item 2-3</li>            
    </ul>  
  </li>    
</ul>

Widget Properties

Like existing HTML widgets, ARIA widgets have properties used to configure their behavior. For example, a <textarea> can be declared read only by adding the readonly attribute. ARIA properties work exactly the same way, but they require an “aria-” prefix.
For example, the previous tree widget can be configured to support multiple selection using the aria-multiselectable attribute.

<ul role="tree" aria-multiselectable="true" tabindex="0"></ul>

ARIA properties can also be used with existing HTML widgets. For example, to declare a button with a popup menu add the aria-haspopup attribute.

<button id="btn-1" type="button" aria-haspopup="true">Show Options</button>

Properties can also be set using JavaScript:

// Using native DOM
document.getElementById("btn-1").setAttribute("aria-haspopup", true);

// Using jQuery
$("#tb-1").attr("aria-haspopup", true);

// Using the set() method of YUI3
Y.one("#tb-1").set("aria-haspopup", true);

The complete set of properties for each ARIA widget are listed alongside each widget role in the ARIA Specification. Click any of the widget roles in the Widget Role Reference section to reveal the widget’s corresponding properties in the ARIA Spec.

Widget States

In addition to properties, ARIA widgets have attributes used to represent states—and they work just like state attributes found in existing HTML widgets. For example, the checked state of an HTML checkbox is represented via the checked attribute:

<input type="checkbox" checked>

ARIA states work exactly the same way, and like the ARIA properties, they require an “aria-” prefix. Returning to the tree widget example, tree items can be expanded and collapsed, and that state is represented using the aria-expanded attribute.

<a href="#group-1" role="treeitem" aria-expanded="true" tabindex="-1">Item 2</a>
<ul id="group-1" role="group">
  <li role="treeitem" tabindex="-1">Item 2-1</li>
  <li role="treeitem" tabindex="-1">Item 2-2</li>
  <li role="treeitem" tabindex="-1">Item 2-3</li>            
</ul>

And like ARIA properties, ARIA states can also be used with existing HTML widgets. For example, add the aria-invalid attribute to indicate the user has entered an invalid value into a text field.

<input type="text" aria-invalid="true">

All of the states for each ARIA widget are listed alongside each widget role in the ARIA Specification. Click any of the widget roles in the Widget Role Reference section to reveal its corresponding states in the ARIA Spec.

Managing States and Properites

The behavior associated with the states and properties of existing HTML widgets are implemented by the browser. For example, the value of the checked attribute is changed automatically when the user checks or unchecks a checkbox.

<input id="checkbox-1" type="checkbox" checked>
<script type="text/javascript">

document.getElementById("checkbox-1").onclick = function (e) {
  console.log(e.target.checked);
};

</script>

Unlike existing HTML widgets, the behaviors associated with ARIA widget states and properties are not managed by the browser. As such, it’s necessary to implement the expected behaviors using JavaScript. To revisit the tree widget example, the initial aria-expanded state of a leaf node might be declared in the markup, but will be toggled in response to click events.

Initial State Declared in Markup

<a id="treeitem-1" href="#group-1" role="treeitem" aria-expanded="false">Item 2</a>

State Changes Managed Using JavaScript:

function onItemClick(e) {
  e.preventDefault();
  var target = e.target;
  target.set("aria-expanded", !(target.get("aria-expanded") === "true"));
}
document.getElementById("treeitem-1").onclick = onItemClick;

Widget Labels

The <label> element has been the tried and true method for labeling existing HTML widgets. ARIA provides several new methods of labeling widgets.

aria-label attribute

Specifies the text label inline, but isn’t visually rendered. Handy when there’s no space for a label on screen and the title attribute is already used to supply an instructional tooltip for sighted users. For example a checkbox in a table cell:

<input type="checkbox" aria-label="Message 1" title="Click to select this message">

aria-labelledby attribute

Specifies the id(s) of the element(s) whose text content labels the control. Useful when the desired label text is already present in another element in the DOM. Remember, if the design doesn’t require the text label be visible, hide it in an accessible way using CSS.

Some example uses of aria-labelledby:

Labeling an alert dialog widget:

<div role="alertdialog" aria-labelledby="hd" aria-describedby="msg">
  <form>
    <fieldset>
      <legend id="hd">Confirm Action</legend>
      <p id="msg">Are you sure you want to submit this form?</p>
      <input type="button" id="ok-button" value="OK">
      <input type="button" id="cancel-button" value="Cancel">
    </fieldset>
  </form>
</div>

Labeling a listbox widget:

<h2 id="hd">Folders</h2>
<ul role="listbox" aria-labelledby="hd" tabindex="0">
  <li role="option" tabindex="-1">Inbox</li>
  <li role="option" tabindex="-1">Drafts</li>
  <li role="option" tabindex="-1">Sent</li>
  <li role="option" tabindex="-1">Spam</li>
  <li role="option" tabindex="-1">Trash</li>
</ul>

aria-describedby attribute

Specifies the id(s) of the element(s) whose text provides a description supplemental to the label. For example: an error message associated with a form field that has invalid user input.

<label for="first-name">First</label>
<input type="text" name="first-name" id="first-name" required aria-required="true" aria-invalid="true" aria-describedby="err-msg">
<p id="err-msg">Cannot be blank.</p>

Widget Styling

Unlike existing HTML widgets, ARIA widgets don’t provide any default presentation. However, most modern browsers support CSS Attribute Selectors, making it easy to style the various ARIA widget types by targeting the role attribute.

ul[role=tree] {
}
div[role=dialog] {
}

Styling States and Properties

Like behaviors, the style for states and properties of existing HTML widgets is automatically managed by the browser. For example, when the user checks a checkbox, a checkmark is added or removed. Disabled controls are automatically rendered grayed out when the disabled attribute is removed.

As mentioned earlier, ARIA widgets states and properties are unmanaged; developers are responsible for defining the visual style associated with widget states and properties. In most modern browsers this can be accomplished using CSS Attribute Selectors. For example, to style a button that has a popup menu:

button[aria-haspopup] {
}

Returning to the tree view widget, style the expanded or collapsed states:

a[aria-expanded=true] + ul[role=group] {
  display: block;
}
a[aria-expanded=false] + ul[role=group] {
  display: none;
}

Widget Role Reference

The ARIA role attribute can only be set to one of the following predefined values. In the interest of helping developers find the right widget role for the job, the roles have been organized into three categories. Each of the following widget roles is linked to the ARIA Spec, which details the properties and states associated with each role.

Individual Atomic Widgets

Overlay Widgets

Composite Widgets / Managed Containers

Tags: , , , ,