Vanilla HTML is limiting and boring. Our clients demand highly engaging and interactive web experiences. And wouldn’t you know, with just a bit of HTML and JavaScript we can craft amazing custom controls, widgets and effects that go far beyond the confines of traditional static markup. But how can we ensure that these custom experiences are both understandable and usable for people with disabilities, and in particular those using assistive technologies such as screen readers?
In this talk, we will look at the basics of making some common custom-built components accessible - covering how browsers and assistive technologies interact, the limitations of HTML, and how ARIA can help make interactive experiences more accessible. In addition, we will explore some of the recent additions in ARIA 1.1, as well as some particular challenges when it comes to traditional ARIA patterns and assistive technologies on mobile/tablet/touch devices.
Evergreen slidedeck at https://patrickhlauke.github.io/aria/presentation/ / https://github.com/patrickhlauke/aria/
3. 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)
6. 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
7. accessibility is a broad subject
• the general concept of accessibility covers a large spectrum of
disabilities / user needs
• accessibility does not just mean "blind users with screen readers"
• sighted keyboard users, users with colour blindness / deficiencies,
deaf / hard of hearing users, users with cognitive disabilities...
• however, ARIA is (almost) exclusively about how to ensure that web
content is correctly conveyed to assistive technologies (screen
readers, screen magnifiers with screen reading capability, etc)
12. <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?
13. compare <div> to a real <button>
faked button versus real <button>
17. 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])
18. separate from the DOM, browsers have an "accessibility tree"
(see chrome://accessibility for an example)
23. 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...
36. ARIA defines HTML attributes
to convey correct role, state,
properties and value
37. at a high level, ARIA defines various role and aria-* attributes that
can be added to your markup
38. 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
39. Roles are categorized as follows:
• Abstract Roles: never used in markup directly - they serve only for
the categorization/definitions
• Widget Roles: interactive controls - simple or composite (made up of
various components)
• Document Structure Roles: define what content/structures represent
(e.g. headings, lists, groups)
• Landmark Roles: identify key parts of a document/page
• Live Region Roles: special areas of the page whose content is
dynamically updated and should be announced by AT
• Window Roles: content that acts as a separate window (dialogs)
40. each role has "states and properties" (e.g. ARIA 1.1 definition for button )
implicit/inherited or defined via aria-* attributes
42. as my former colleague Karl Groves succinctly put it,
ARIA helps answer the questions:
what is this thing and what does it do?
43. what information does ARIA provide?
• role: what type of "thing" is this? (e.g. 'button', 'main content')
• state: what is its situation? (e.g. 'disabled', 'checked', 'expanded')
• name: what is its name/label?
• relationship: is it tied to any other elements in the page somehow?
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")
46. use ARIA to:
• make custom widgets understandable to assistive technology users
• programmatically indicate relationships between elements
• notify users of dynamic updates
• hide irrelevant visible content from assistive technology
• remediation of inaccessible content without completely recoding
47. ARIA roles and attributes: restrictions
• certain role s only make sense as part of a specific complex widget
• some aria-* attributes are global
• other aria-* attributes only make sense for particular role
• not all roles/attributes are supported/implemented consistently
everywhere
48. what ARIA doesn't do...
ARIA is not magic : it only changes how assistive technology
interprets content. specifically, ARIA does not :
• make an element focusable
• provide appropriate keyboard bindings
• change browser behavior
• automatically maintain/update properties
• change visible appearance
all of this is still your responsibility...
49. no ARIA is better than bad ARIA
• ARIA roles/attributes are a "promise" to users / assistive technologies
(e.g. "this component is actually a button...") – you must ensure that it
then behaves correctly
• if you're not sure how to represent a particular complex widget or
control, don't just throw ARIA roles and attributes at your markup –
you're more likely to make things worse / more confusing / non-
functional for assistive technology users
W3C - WAI-ARIA 1.1 Authoring Practices
50. WAI-ARIA spec can be dry/technical - use for reference
W3C - WAI-ARIA 1.1 Authoring Practices more digestible.
51. in principle ARIA can be used
in all markup languages
(depending on browser support )
59. 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.
“
60. 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>
61. 2. don't change native
semantics
unless you really have to / know what you're doing
62. 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
63. 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
65. 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
67. 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
77. ARIA and HTML5
• ARIA is used when building things that native HTML can't do
• for many years, ARIA was a "bridging technology" to overcome HTML
semantic limitations
• HTML5 introduced new elements, element types, attributes that solve
some of these situations
• there are still things that HTML can't express, so ARIA is here to stay...
85. <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
• add role="heading"
• if more than one hierarchical level, and can't be inferred from
structure, add explicit aria-level
86. <div role="list" >
<div role="listitem" >...</div>
<div role="listitem" >...</div>
<div role="listitem" >...</div>
...
</div>
example: list/listitem
• add role="list" and role="listitem"
• generally more complex (big markup structures that boil down to
essentially "a list of things...")
87. <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
89. 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" )
91. adapted from HTML5 Doctor - Designing a blog with html5
example: blog structure
92.
93. why define landmarks?
• users of assistive technologies can more easily find areas of your
page/app
• AT keyboard controls to navigate to/between landmarks
• overview dialogs listing all landmarks (e.g. NVDA)
101. <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
102. <span tabindex="0" class="..." role="button" >Button!</span>
<div tabindex="0" class="..." role="button" >Button!</div>
<a href="#" class="..." role="button" >Button!</a>
• add role="button"
• make sure it's focusable
• add handling of SPACE (and in some cases ENTER )
103. assuming there's a click handler:
foo.addEventListener('keyup', function(e) {
// Space key
if (e.keyCode === 32) {
// stop default behavior (usually, scrolling)
e.preventDefault();
// trigger the existing click behavior
this.click();
}
});
104. 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) {
e.preventDefault();
this.click();
}
});
}
106. • default HTML does not offer a simple on/off toggle
• "CSS only" <input type="checkbox"> hacks – but these may be
confusing to AT users/not always appropriate
• ARIA can be used to enhance native elements – start off with closest
match, like an actual <button> , and extend from there
107. 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
108. <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
• these are HTML attributes and must be read/written using
getAttribute() / setAttribute()
• and even boolean attributes take string values
109. <button class="... " aria-pressed="true" >Toggle</button>
button[aria-pressed="true"] { ... }
• bonus: use CSS attribute selectors – no need for extra CSS class (this
also helps debugging – easier to spot visually when ARIA
roles/properties aren't correctly set)
example: toggle with aria-pressed and simplified CSS
toggled
button.toggled { ... }
110. 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
121. • ARIA Practices 1.0 suggested using a tab panel pattern (we'll briefly
look at this in a moment)
• ARIA Practices 1.1 is more pragmatic: an accordion is seen simply as a
series of disclosure widgets (see ARIA Practices 1.1 - 3.1 Accordion)
125. • when triggered, focus should move to the dialog
• focus should be maintained inside the dialog
example: modal dialog with focus management
...but for assistive tech users, it's still not clear what is happening...
131. complex widgets and focus
• some complex widgets (group of radio buttons, menus, listboxes, tab
lists) only have a single "tab stop"
• focus/interactions within the widget are handled programmatically
• TAB / SHIFT + TAB moves to the next widget, not to sub-components
132. there are two approaches for focus:
• roving tabindex : only one element inside widget has
tabindex="0" , all others tabindex="-1"
• aria-activedescendant : focus remains on the widget container (or
one specific part of the widget), then the aria-activedescendant
attribute points to the id of the "active" subcomponent
W3C WAI-ARIA 1.1 Authoring Practices - 5.6 Keyboard Navigation
Inside Components
133. <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
134. 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).
137. <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?
138. 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.
139. ARIA live regions
• announce a change on content without moving focus to it
• aria-live : off (default), polite , assertive
140. <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
141. • some role s have implicit live region (e.g. role="status" and
role="alert" ), as do some markup elements (e.g. <output> )
142. <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
143. 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.
• sending something with role="alert" as part of your static HTML
document has no effect – AT won't announce this when the page is
loading
• creating a dynamic element with aria-live="..." or an implicit
live region role and filling it with content right away will (depending
on timing) usually not give browser/AT enough time to "see" the
element and notice the change ... again resulting in no announcement
Mozilla Developer Network: ARIA Live Regions
144. // 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
145. by default, live regions only announce any new/changed content
(with some browser/AT variations). however, this can be controlled:
• aria-atomic : true / false ("treat the live region as one atomic unit
and announce everything")
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).
146. ARIA has many more
complex/composite widgets and
structures
150. as useful as ARIA is, it is far from perfect ...
• some patterns rooted deeply in native (Windows95 style) application
paradigms – and as a result, quite limited/restrictive
• straying from these patterns / using ARIA incorrectly can make
things worse for users
• certain roles/patterns define specific keyboard interactions – but
work badly or not at all in touchscreen + AT scenarios
• particularly for new roles/attributes (ARIA 1.1) and complex patterns,
browser and AT support may be lacking
152. <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 Practices 1.1 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
153. ARIA 1.1 menu (role)
specific child elements only ( menuitem , menuitemcheckbox , menuitemradio )
154. menu with a form control - not a valid ARIA menu...
162. • not everybody codes their complex applications from scratch using
hand-made HTML/JavaScript/CSS
• many ready-made libraries, scripts and frameworks offer at least
basic ARIA support (by default, or as an "optional" setting or module)
• particularly open source projects offer good opportunities to get
involved – file issues, submit pull requests, improve documentation
• lack of accessibility/ARIA support likely not down to malice, but a
lack of knowledge or even understanding of the problem...
168. what ARIA is/isn't ...
• ARIA conveys "what is this thing and what does it do?" to assistive
technologies
• ARIA does not influence browser behavior itself – you need to manage
keyboard handling, state, etc. yourself
• ARIA is vast and complex, depends on browser/AT support, has
shortcomings
169. pragmatic approach to using ARIA
• understand/break-up complex app into simple component elements
• you don't always need ARIA – appropriate HTML usually best
• No ARIA is better than Bad ARIA
• consult ARIA Practices to see if they match an existing pattern
• follow the rules of ARIA
• check main ARIA specification for correct syntax, expected attributes,
valid values
• test in actual browser/AT