An introduction to Accessible Rich Internet Applications


An introduction to Accessible Rich Internet Applications

Patrick H. Lauke / The Paciello Group / AccessU / 15 May 2018

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

Polymer Project

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

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 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

Operating systems and other platforms provide interfaces that expose information about objects and events to assistive technologies
Java Accessibility API [JAPI], Microsoft Active Accessibility [MSAA], the Mac OS X Accessibility Protocol [AXAPI], the Gnome Accessibility Toolkit (ATK) [ATK], and IAccessible2 [IA2]

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; right-click/context menu on an element also includes the 'Inspect Accessibility Properties' option

Firefox Accessibility Inspector (version 61+)

(enable devtools.accessibility.enabled in about:config)

chrome://accessibility page, showing all extensions and open tabs, with the option to display the raw accessibility tree for each

chrome://accessibility view of the raw accessibility tree

aViewer window showing MSAA, ARIA, HTML and UIAutomation information of a focused element in Firefox

Accessibility Viewer (aViewer)

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

James Craig - Using ARIA 1.0 and the WebKit Accessibility Node Inspector

Xcode's Accessibility Inspector, showing accessibility tree/attributes for a selected element in Chrome

Xcode Accessibility Inspector
(but for Chrome, remember to turn on accessibility mode in chrome://accessibility)

compare <div> to a real <button>

faked button versus real <button>

aViewer showing that the faked button is exposed as a simple 'div'
aViewer showing that a real button element is exposed with a role of 'button', with additional default values being exposed such as MSAA accName

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 succintly 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="..."

faked button with appropriate role

aViewer showing that a faked button using a 'div', but with correct role=button, is exposed the same way as a native HTML 'button' element 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...

ARIA support

"support" does not mean that everything works, though...

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


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"

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>

do this instead:

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

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

don't do this:

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

do this instead:

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

otherwise the list is no longer a list
(e.g. AT won't announce "list with X items...")

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 and widgets sections of the WAI-ARIA 1.1 Authoring Practices

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>

5. interactive elements must have an accessible name

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

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

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

<span tabindex="0" role="button" title="Delete">
    <span class="glyphicon glyphicon-remove"></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 tabindex="0" role="button" aria-labelledby="...">
    <span class="glyphicon glyphicon-remove"></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)

WAI-ARIA spec can be dry/technical - use for reference
W3C - WAI-ARIA 1.1 Authoring Practices more digestible.


ARIA is used when building/denoting things that native HTML can't do

HTML5 introduced new elements, element types, attributes that solve some of these situations

Using aViewer we see that native HTML5 input with type='range' is exposed correctly as a 'slider'

example: HTML5 slider

W3C HTML Accessibility API Mappings 1.0

HTML5 accessibility

HTML5 now also includes WAI-ARIA ...

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

...and 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>

example: list/listitem

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">
		<td>Layout column 1</td>
		<td>Layout column 2</td>

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")


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


<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 key
  if (e.keyCode === 32) {
	// stop default behavior (usually, scrolling)
	// trigger the existing click behavior

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.keyCode === 32) {

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.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


<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


<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

<div ... >
	<div>My custom dialog</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...

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

example: modal dialog with focus management and ARIA

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

<div id="wrapper">>
<div role="dialog" ...</div>
function openModal() {
function closeModal() {

example: modal dialog with aria-hidden

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

function openModal() {
function closeModal() {

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

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

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

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

managing focus

complex widgets and focus

keyboard navigation within widgets

W3C WAI-ARIA 1.1 Authoring Practices - 5.6 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 Practices 1.1 - 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 Practices 1.1 Combobox with Listbox Popup

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...

example: status bar

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

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.

// 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

// add the new div to the page


by default, live regions only announce any new/changed content. however, this can be controlled:

example: live region clock and live region clock 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

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 role="tabpanel" id="panel1" aria-hidden="false">...</div>
<div role="tabpanel" id="panel2" aria-hidden="true">...</div>
<div role="tabpanel" id="panel3" aria-hidden="true">...</div>

example: ARIA Practices 1.1 Tabs with Automatic Activation

variations on this theme: 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...


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

example: ARIA Practices 1.1 Navigation Menubar

most suitable for real "application-like" web-apps - arguably not appropriate for general "website navigation"

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)

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 Android, showing the announcement to use volume keys to change value

ARIA Practices 1.1 - Horizontal Slider on Android / Chrome / TalkBack
(use volume keys...)

Screenshot of the slider on iOS

ARIA Practices 1.1 - Horizontal Slider on iOS / Safari / VoiceOver
(swipe up or down...)

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

ARIA Practices 1.1 - Combobox with Listbox Popup
on Android / Chrome / TalkBack

Screenshot of the combobox on iOS

ARIA Practices 1.1 - Combobox with Listbox Popup
on iOS / Safari / VoiceOver

frameworks, web components, angular, etc?

Bootstrap - Accessibility

AngularJS Developer Guide - Accessibility with ngAria


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 Connaly

what ARIA is/isn't ...

pragmatic approach to using ARIA

get in touch


Creative Commons: Attribution Non-Commercial Share-Alike