WAI-ARIA

An introduction to Accessible Rich Internet Applications

WAI-ARIA

An introduction to Accessible Rich Internet Applications

Patrick H. Lauke

what is ARIA
and why do we need it?

the problem

it's "easy" (in most cases) to make static web content accessible, but nowadays the web strives to be an application platform

complex web applications require structures (e.g. interactive controls) that go beyond what regular HTML offers (though some of this introduced with HTML5 ... more on that later)

jQuery UI

webcomponents.org

the problem

when building interactive controls, some (too many) web developers go "all out" on the JavaScript/CSS, but ignore/sidestep regular HTML controls completely (where native HTML equivalents are available) and ignore how these controls are exposed to assistive technologies

accessibility is a broad subject

github.com/patrickhlauke/aria

code examples here are simplified
(but will hopefully convey the right concepts)

Warning sign with lightning bolt and text: Danger 415 volts
<div onclick="...">Test</div>

faked button

for a sighted mouse / touchscreen user this is a button...
but what about keyboard users?

<div tabindex="0" onclick="...">Test</div>

faked button with focus

now we can at least focus it...but can we activate it?

<div tabindex="0" onkeyup="..." onclick="...">Test</div>

faked button with focus and keyboard handling

for a sighted mouse / touchscreen / keyboard user this is a button...
but what about assistive technology users?

compare <div> to a real <button>

faked button versus real <button>

"test"
versus
"test button – to activate press SPACE bar"

the problem

generic/inappropriate HTML elements, with extra JavaScript/CSS on top...but they're still recognised and exposed as <span>, <div>, etc

the interplay of browser and assistive technology

Diagram: when focused on an element, such as a button, the browser exposes information (name, value, role, state, description) to the OS accessibility API, which in turn is accessed by assistive technologies like JAWS

Operating systems provide interfaces that expose information about objects and events to assistive technologies
(e.g. Microsoft Active Accessibility [MSAA], the Mac OS X Accessibility Protocol [AXAPI], IAccessible2 [IA2])

Accessibility Tree

separate from the DOM, browsers build a matching "accessibility tree":

Comparison of a typical DOM structure and the matching accessibility tree structure

a typical DOM (as shown in a browser's developer tools, which simplifies some aspects like text nodes) and the matching accessibility tree

Marco Zehe: Why accessibility APIs matter

assistive technologies

assistive technologies

inspection tools

inspection tools

test using assistive technologies (e.g. screenreaders), however...

assistive technologies often use heuristics to repair incomplete/broken accessibility API information - so we want to check what's actually exposed to the OS/platform.

of course, browsers also have bugs/incomplete support...

Firefox Accessibility Inspector panel in Firefox Developer Tools, showing the accessibility tree and accessibility properties - exposed by Firefox to the platform/OS accessibility API - of an element in a page

Firefox Accessibility Inspector

Chrome DevTools' accessibility panel, showing the accessibility tree and accessibility properties - exposed by Chrome to the platform/OS accessibility API - of an element in a page

Chrome DevTools Accessibility pane

Chrome DevTools' full accessibility tree, showing the accessibility tree and accessibility properties - exposed by Chrome to the platform/OS accessibility API - of an element in a page

Chrome DevTools (Preview) Explore the full-page accessibility tree

Safari's Web Inspector, showing a separate 'Accessibility' section under its 'Node' inspection sidebar, listing basic information such as the current node's computed role

Web Inspector Elements tab

compare <div> to a real <button>

faked button versus real <button>

Chrome Developer Tools' accessibility panel, showing that the faked button is exposed as a simple 'div'
Chrome Developer Tools' accessibility panel, showing that a real button element is exposed with a role of 'button'

if you use custom (not standard HTML) widgets,
use ARIA to ensure correct info is exposed

what is ARIA?

W3C - Accessible Rich Internet Applications (WAI-ARIA) 1.1

ARIA defines HTML attributes to convey correct role, state, properties and value

Diagram: ARIA defines 'role' and 'aria-*' attributes

at a high level, ARIA defines various role and aria-* attributes that can be added to your markup

Diagram: the complete (and complex) ARIA RDF model

W3C - WAI-ARIA 1.1 - 5.3 Categorization of Roles

the whole model is vast and complex...and thankfully you don't need to remember this

Roles are categorized as follows:

Screenshot: the complete definition for the 'button' role in ARIA 1.1, listing - among other things - its 'Supported States and Properties' and 'Inherited States and Properties'

each role has "states and properties" (e.g. ARIA 1.1 definition for button)
implicit/inherited or defined via aria-* attributes

this is all complex and confusing...

Screenshot: video of the presentation by Karl Groves

as my former colleague Karl Groves succinctly put it,
ARIA helps answer the questions:
what is this thing and what does it do?

what information does ARIA provide?

this information is mapped by the browser to the operating system's accessibility API and exposed to assistive technologies.

extra benefit: once AT understands meaning/purpose, it can automatically announce specific hints and prompts
(e.g. JAWS "... button - to activate, press SPACE bar")

<div tabindex="0"
role="button" onkeyup="..."
onclick="...">Test</div>

faked button with appropriate role

Chrome Developer Tools' accessibility panel, showing that a faked button using a 'div', but with correct role=button, is exposed the same way as a native HTML 'button' element

use ARIA to:

ARIA roles and attributes: restrictions

what ARIA doesn't do...

ARIA is not magic: it only changes how assistive technology interprets content. specifically, ARIA does not:

all of this is still your responsibility...

no ARIA is better than bad ARIA

W3C - ARIA Authoring Practices Guide (APG)

WAI-ARIA spec can be dry/technical - use for reference
W3C - ARIA Authoring Practices Guide (APG) is more digestible.

in principle ARIA can be used in all markup languages
(depending on browser support)

Using ARIA to enhance SVG accessibility

...but we'll just focus on
ARIA in HTML

W3C - ARIA in HTML

W3C - Using ARIA

the 5 rules of ARIA use

1. don't use ARIA

Fight Club spoof picture, with Steve Faulkner: The first rule of ARIA club is...you don't use ARIA

If you can use a native HTML element [HTML5] or attribute with the semantics and behaviour you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so.

you can use a <span> to behave as, and be exposed just like, a link...


<span tabindex="0" role="link"
      onclick="document.location='...'"
      onkeyup="...">link</span>

example: link

...but why would you?

unless there's a very good reason, just use <a href="...">...</a>

2. don't change native semantics

unless you really have to / know what you're doing

don't do this:

<h1 role="button">heading button</h1>

otherwise the heading is no longer a heading
(e.g. AT users can't navigate to it quickly)

you can do this instead:

<h1><span role="button">heading button</span></h1>

or, in accordance with the first rule or ARIA:

<h1><button>heading button</button></h1>

example: heading button

don't do this:

<ul role="navigation">
    <li><a href="...">...</a></li>
    ...
</ul>

do this instead:

<div role="navigation">
    <ul>
        <li><a href="...">...</a></li>
        ...
    </ul>
</div>

or list is no longer a list (e.g. AT won't say "list with X items...")

example: list navigation

3. make interactive ARIA controls keyboard accessible

All interactive widgets must be focusable and scripted to respond to standard key strokes or key stroke combinations where applicable. [...]

Refer to the keyboard and structural navigation and design patterns sections of the ARIA Authoring Practices Guide

4. don't "neutralise" focusable elements

don't use role="presentation" or aria-hidden="true" on a visible focusable element. Otherwise, users will navigate/focus onto "nothing".


<!-- don't do this... -->

<button role="presentation">press me</button>

<button aria-hidden="true">press me</button>

<span tabindex="0" aria-hidden="true">...</span>

example: neutralised elements

Screenshot of Chrome Developer Tools' accessibility panel, showing how aria-hidden elements, and elements whose ancestor is aria-hidden, are denoted

Chrome DevTools indicates when a node is hidden
(directly, or due to an ancestor being hidden)

Screenshot of Firefox accessibility inspector - aria-hidden nodes are not shown at all in the accessibility tree representation

aria-hidden removes nodes from the accessibility tree
(as seen here in Firefox's accessibility inspector)

5. interactive elements must have an accessible name


<!-- don't do this... -->

<span tabindex="0" role="button">
    <span class="glyphicon glyphicon-remove"></span>
</span>

<span tabindex="0" role="button">
    <span class="glyphicon glyphicon-remove">
        <span class="...">Delete</span>
    </span>
</span>

<span tabindex="0" role="button" title="Delete">
    <span class="glyphicon glyphicon-remove"></span>
</span>

refer to WAI-ARIA 1.1 - 5.2.7. Accessible Name Calculation


<span tabindex="0" role="button" aria-label="Delete">
    <span class="glyphicon glyphicon-remove"></span>
</span>

<span tabindex="0" role="button" aria-labelledby="...">
    <span class="glyphicon glyphicon-remove"></span>
</span>
...
<span id="..." class="...">Delete</span>

refer to WAI-ARIA 1.1 - 5.2.7. Accessible Name Calculation

Screenshot of Chrome Developer Tools' accessibility panel, showing details about which attributes - content, title, aria-label, aria-labelledby - contribute to the element's name

Chrome DevTools' accessibility panel can help understand which attributes contribute to an element's name

example: accessible name calculation

side note: aria-label / aria-labelledby / aria-describedby and arbitrary elements
(see Short note on aria-label, aria-labelledby, and aria-describedby)

ARIA and HTML5

ARIA and HTML5

Using Chrome DevTools' accessibility panel we see that a native HTML5 input with type='range' is exposed correctly as a 'slider'

example: HTML5 range input

W3C HTML Accessibility API Mappings 1.0

of course you can (and for many complex widgets, must) still use
WAI-ARIA in HTML5

W3C Validator result, showing an error for an inappropriately used ARIA role

side note: you can validate pages with (static) ARIA validator.w3.org

common structures and widgets

(not an exhaustive list - enough to understand concepts)

using ARIA to provide structure


<p class="1" role="heading" aria-level="1">Heading 1</p>
...
<p class="h2" role="heading" aria-level="2">Heading 2</p>
...
<p class="h3" role="heading" aria-level="3">Heading 3</p>
...

example: headings


<div role="list">
    <div role="listitem">...</div>
    <div role="listitem">...</div>
    <div role="listitem">...</div>
    ...
</div>

example: list/listitem

<img> is identified as an image by assistive technologies, and you can provide alternative text.


<img src="..." alt="alternative text">

if you're using embedded <svg>, use ARIA to achieve the same:


<svg role="img" aria-label="alternative text"> ... </svg>

example: embedded SVG image

removing semantics

if your page/app uses inappropriate markup, ARIA can be used to remove semantic meaning. useful for remediation if markup cannot be changed.


<table role="presentation">
    <tr>
        <td>Layout column 1</td>
        <td>Layout column 2</td>
    </tr>
</table>

example: layout table remediation

ARIA 1.1 introduced role="none" as an alias for role="presentation" – they are equivalent (and older browsers/AT likely better off still using role="presentation")

landmarks

Classic blog structure using div elements with id attributes 'header', 'sidebar', 'content', 'footer' and individual posts marked up with div elements given class name 'post'

adapted from HTML5 Doctor - Designing a blog with html5

example: blog structure

Classic blog structure using div elements, now complemented with ARIA role attributes 'banner', 'navigation', 'main', 'contentinfo' and individual posts with role of 'article'
why define landmarks?
Illustration of how the blog structure with ARIA role attributes is listed in NVDA's Elements List > Landmarks

example: blog structure with ARIA

doesn't HTML5 solve this?

Classic blog structure, recoded using HTML5 structural elements: header, nav, main, article, footer

adapted from HTML5 Doctor - Designing a blog with html5

example: blog structure with HTML5

Illustration of how the blog structure with HTML5 structural elements is listed in NVDA's Elements List > Landmarks

using ARIA for
simple/standalone widgets

my JavaScript sucks...
(but will hopefully convey the right concepts)

Warning sign with lightning bolt and text: Danger 415 volts

button


<span class="...">Button?</span>

<div class="...">Button?</div>

<a href="#" class="...">Button?</a>

example: button

while using a link is slightly less evil, as at least it receives keyboard focus by default, it's still not correct: links are meant for navigation, not in-page actions or form submissions


<span tabindex="0" class="..." role="button">Button!</span>

<div tabindex="0" class="..." role="button">Button!</div>

<a href="#" class="..." role="button">Button!</a>

assuming there's a click handler:


foo.addEventListener('keyup', function(e) {
  // Space or Enter key
  if (e.key === " " || e.key === "Enter") {
    // stop default behavior (usually, scrolling)
    e.preventDefault();
    // trigger the existing click behavior
    this.click();
  }
});

you could even do it "in one go" for all your faked buttons, assuming they have the correct role="button", with querySelectorAll and attribute selectors:


var buttons = document.querySelectorAll("[role='button']");
for (var i=0; i<buttons.length; i++) {
  buttons[i].addEventListener('keyup', function(e) {
    if (e.key === " " || e.key === "Enter") {
      e.preventDefault();
      this.click();
    }
  });
}

toggle button

let's assume we implement this with JavaScript to purely add a CSS classname:


<button class="...">Toggle</button>

<button class="... toggled">Toggle</button>

example: toggle

in real applications, you'll likely keep track of the state in some additional way – this is only for illustrative purposes


<button class="..." aria-pressed="false">Toggle</button>
<button class="... toggled" aria-pressed="true">Toggle</button>
foo.getAttribute("aria-pressed");
foo.setAttribute("aria-pressed", "true");
foo.setAttribute("aria-pressed", "false");

add aria-pressed and dynamically change its value

example: toggle with aria-pressed


<button class="... toggled" aria-pressed="true">Toggle</button>
button.toggled { ... }
button[aria-pressed="true"] { ... }

example: toggle with aria-pressed and simplified CSS

if your actual label text changes when toggling, aria-pressed is likely not necessary (could actually be more confusing for user)


<button ...>Mute</button>
if (...) {
    this.innerHTML = "Mute";
    ...
} else {
    this.innerHTML = "Unmute";
    ...
}

example: toggle with a changing name/label and ARIA versus toggle with a changing name/label only

checkbox


<span tabindex="0" class="...">Option</span>

<span tabindex="0" class="... checked">Option</span>

example: checkbox


<span tabindex="0" role="checkbox"
      aria-checked="false" class="...">Option</span>

<span tabindex="0" role="checkbox"
      aria-checked="true" class="... checked">Option</span>

example: checkbox with aria-checked

radio button


<span tabindex="0" class="...">Yes</span>
<span tabindex="0" class="... selected">No</span>
<span tabindex="0" class="...">Maybe</span>

example: radio button


<span tabindex="0" role="radio"
      aria-checked="false" class="...">Yes</span>
<span tabindex="0" role="radio"
      aria-checked="true" class="... selected">No</span>
<span tabindex="0" role="radio"
      aria-checked="false" class="...">Maybe</span>

example: radio button with ARIA (but note incomplete focus handling)

disclosure widget

<button ...>More details</button>
<div class="show" ...> ... </div>

example: disclosure widget

<button ... aria-expanded="true"
    aria-controls="disclosure1">More details</button>
<div class="show" ... id="disclosure1"> ... </div>

example: disclosure widget with aria-expanded / aria-controls

accordion

<button ... aria-expanded="true"
    aria-controls="accordion1">Item 1</button>
<div class="show" ... id="accordion1"> ... </div>

<button ... aria-expanded="false"
    aria-controls="accordion2">Item 2</button>
<div class="show" ... id="accordion2"> ... </div>

<button ... aria-expanded="false"
    aria-controls="accordion3">Item 3</button>
<div class="show" ... id="accordion3"> ... </div>

example: accordion using disclosure widgets

modal dialog


<button>Launch...</button>
...
<div ... >
    <div>My custom dialog</div>
    ...
</div>

example: modal dialog

...but focus handling is not really correct...

example: modal dialog with focus management

...but for assistive tech users, it's still not clear what is happening...


<button>Launch...</button>
...
<div role="dialog"
     aria-labelledby="modalDialogHeader"
     aria-describedby="modalDialogDescription" ... >
  <div id="modalDialogHeader">My custom dialog</div>
  <div id="modalDialogDescription">
    ... 
  </div>
  ...
</div>

example: modal dialog with focus management and ARIA

...almost perfect, but assistive tech users can still navigate out of the modal...

<div id="wrapper">
  <button>Launch...</button>
</div>
...
<div role="dialog" ...> ... </div>
function openModal() {
 document.getElementById("wrapper")
         .setAttribute("aria-hidden","true"); ...
}
function closeModal() {
 document.getElementById("wrapper")
        .removeAttribute("aria-hidden"); ...
}

example: modal dialog with aria-hidden

note: aria-hidden does not prevent regular keyboard focus!

function openModal() {
 document.getElementById("wrapper").setAttribute("inert","");
 ...
}
function closeModal() {
 document.getElementById("wrapper").removeAttribute("inert");
 ...
}

example: modal dialog with inert

note: inert does hide elements from accessibility tree and remove behavior such as keyboard focusability. however, not natively supported yet – use the inert polyfill


<button>Launch...</button>
...
<div role="dialog" aria-modal="true"
     aria-labelledby="modalDialogHeader"
     aria-describedby="modalDialogDescription" ... >
  <div id="modalDialogHeader">My custom dialog</div>
  <div id="modalDialogDescription">
    ... 
  </div>
  ...
</div>

example: modal dialog with aria-modal (new in ARIA 1.1)

...but you still need to do the focus management yourself...

see also: Scott O'Hara: The current state of modal dialog accessibility

managing focus

complex widgets and focus

there are two approaches for focus:

ARIA Authoring Practices Guide - Keyboard Navigation Inside Components


	<span tabindex="-1" role="radio"
		  aria-checked="false" class="...">Yes</span>
	<span tabindex="0" role="radio"
		  aria-checked="true" class="... selected">No</span>
	<span tabindex="-1" role="radio"
		  aria-checked="false" class="...">Maybe</span>
	

only one radio button inside the group has focus. changing the selection using CURSOR keys, dynamically changes tabindex, aria-checked and sets focus() on the newly selected radio button

example: ARIA Authoring Practices Guide - Radio Group Using Roving tabindex

not all complex widgets lend themselves to "roving" tabindex – e.g. role="combobox" needs aria-activedescendant, as actual focus must remain inside the textbox.

example: ARIA Authoring Practices Guide - Editable Combobox With List Autocomplete Example

this approach can be complex, and not always supported by assistive technologies (particularly on mobile).

live regions

making assistive technology
aware of content changes

<div id="output"></div>
var o = document.getElementById("output");
o.innerHTML = "Surprise!"; // show the notification

example: notification as result of button press

but how can AT users be made aware of the notification / content change?

one way to notify users of assistive technologies of new content (a new element added to the page, made visible, a change in text) is to move focus() programmatically to it


<div id="output" tabindex="-1"></div>
var o = document.getElementById("output");
o.innerHTML = "Surprise!"; // show the notification
o.focus(); // move focus to the notification

but this is not always possible, as it would interrupt the user's current actions...

example: notification via focus() and a more problematic example simulating a long-running function.

ARIA live regions

<div id="output" aria-live="polite"></div>
var o = document.getElementById("output");
o.innerHTML = "Surprise!"; // show the notification

example: notification via aria-live

bonus points: set aria-disabled="true" on the control, and optionally aria-busy="true" on the notification / section of the page that is getting updated. see notification via aria-live, with aria-busy and aria-disabled


<span role="status">
    some form of status bar message...
</span>

example: status bar


<span role="alert">
    an alert message (no user interaction)
</span>

example: alert

ARIA live regions need to be "primed" first – the browser and AT need to realize there's a live region, and start watching for changes.

Mozilla Developer Network: ARIA Live Regions

// create a new div element 
var newDiv = document.createElement("div");

// set aria-live property
newDiv.setAttribute("aria-live", "polite");

// and give it some content 
var newContent = document.createTextNode("Surprise!"); 

// add the text node to the newly created div
newDiv.appendChild(newContent);

// add the new div to the page
document.body.appendChild(newDiv);

// ... WON'T ACTUALLY WORK

by default, live regions only announce any new/changed content (with some browser/AT variations). however, this can be controlled:

example: live region and live region with aria-atomic

(there's also aria-relevant, but it is badly supported/pointless - see Aaron Leventhal and Rob Dodson: Why authors should avoid aria-relevant).

ARIA has many more
complex/composite widgets and structures

example:
tab panels


<div role="tablist" ...>
  <div role="tab" aria-controls="panel1"
       aria-selected="true"...>Tab 1</div>
  <div role="tab" aria-controls="panel2" ...>Tab 2</div>
  <div role="tab" aria-controls="panel3" ...>Tab 2</div>
</div>

<div role="tabpanel" id="panel1">...</div>
<div role="tabpanel" id="panel2" aria-hidden="true">...</div>
<div role="tabpanel" id="panel3" aria-hidden="true">...</div>

example: ARIA Authoring Practices Guide - Tabs with Automatic Activation

variations: Marco Zehe: Advanced ARIA tip #1: Tabs in web apps

not appropriate if you're just marking up a site navigation...

some shortcomings / problems with ARIA

as useful as ARIA is, it is far from perfect...

example:
menu/menubar


<div role="menubar">
  <div role="menuitem" ...>...</div>
  <div role="menu">
    <div role="menuitem" ...>...</div>
    <div role="menuitem" ...>...</div>
    <div role="menuitem" ...>...</div>
    ...
  </div>
  ...
</div>

example: ARIA Authoring Practices Guide - Navigation Menubar

most suitable for "application-like" web-app menus, not for general "website navigation"

Marco Zehe: WAI-ARIA menus, and why you should generally avoid using them

Screenshot of the warning note on ARIA design patterns and touch device support in the 'Using ARIA' spec

ARIA 1.1 menu (role)

specific child elements only (menuitem, menuitemcheckbox, menuitemradio)

Example of a non-standard dropdown with a search form field - this cannot be expressed with ARIA, as it doesn't allow anything other than menu items, menu checkboxes, menu radio buttons

menu with a form control - not a valid ARIA menu...

example:
touchscreen + assistive technology

Screenshot of the warning note on ARIA design patterns and touch device support in the 'Using ARIA' spec

Using ARIA - 2.15 ARIA Design Patterns and Touch Device Support

Montage of screenshots for slider on old Android, showing the announcement to use volume keys to change value

ARIA Authoring Practices Guide - Color Viewer Slider Example
on older Android / Chrome / TalkBack
("Use volume keys to adjust")

Montage of screenshots for slider on newer Android, showing that the slider is announced as a slider, double-tap to activate, but then no further feedback

ARIA Authoring Practices Guide - Color Viewer Slider Example
on more recent Android / Chrome / TalkBack
("Double-tap to activate" but then no further announcement)

A native HTML input slider with Talkback message: 'Swipe up' or 'Swipe down' to adjust

Native <input type="range">
on more recent Android / Chrome / TalkBack
("'Swipe up' or 'Swipe down' to adjust")

Screenshot of the slider on iOS

ARIA Authoring Practices Guide - Color Viewer Slider Example
on recent iOS / Safari / VoiceOver
("Swipe up or down" but it now works thanks to...)

Montage of the AOM caniuse page, with Phase 2: Synthesized keyboard events aka User action events from Assistive Technology highlighted

Can I Use Accessibility Object Model (AOM)
Safari/VoiceOver now send synthesized keyboard events,
but no support in Chromium or Gecko

Screenshot of the combobox on iOS

ARIA Authoring Practices Guide - Editable Combobox With List Autocomplete Example
on iOS / Safari / VoiceOver

Montage of screenshots for combobox on Android, showing no announcement of the combobox appearing

ARIA Authoring Practices Guide - Editable Combobox With List Autocomplete Example
on Android / Chrome / TalkBack

frameworks, web components, angular, react, etc?

Bootstrap - Accessibility

AngularJS Developer Guide - Accessibility with ngARIA

React - Accessibility

recap...

ARIA is not a magic bullet

Diagram: the remarkable path of the pristine bullet according to Warren Commission (showing entry and exit wounds on Kennedy and Connally

what ARIA is/isn't ...

pragmatic approach to using ARIA

get in touch

@patrick_h_lauke@mastodon.social
github.com/patrickhlauke/aria
splintered.co.uk

Creative Commons: Attribution Non-Commercial Share-Alike