aria a11ycamp-bay-2015
TRANSCRIPT
Accessible Rich Internet
Applications:
Beyond the Basics
Dylan Barrell
Twitter: @dylanbarrell
GitHub: dylanb
http://unobfuscated.blogspot.com/
http://www.deque.com/
8 trillion
$8 trillion
15%
1 billion
P
O
U
R
Perceivable
Operable
Understandable
Robust
Role
Name
State(s)
Value
Keyboard (and gestures) – add graphic of a Braille keyboard
• Accessibility and the DOM
– DOM tree• Semantic structure
• Styled with CSS
• Mouse and Keyboard behavior implemented by browser
• Default mapping to the accessibility tree
– Accessibility tree• Representation of the accessibility information
• Each element has a name, a role, a value and a state
• Interpreted by Assistive technology
• Used in combination with the Accessibility API
– Composed Tree (web components)
Insert periodic table of ARIA roles
• ARIA roles:
– Provide the ability to control the transition
between application (forms) mode and
document mode
– Provide more native announcments for
widgets like menus, tabs, sliders etc.
– Provide for the ability to control
announcements when updates occur away
from the focus
– Provide much more control over the structure
of the document and how someone navigates
around it
<div id=”myId" class=”…”>
<button class=”…” id=”…”
role="slider”
aria-labelledby=”sliderLabelID”
aria-valuemin="0" aria-valuemax="100”
aria-valuenow="0" aria-valuetext="0%”>
</button>
</div>
• Adding role:
– Changes the mapping to the accessibility API
– Does not change the behavior
• Focussability
• Keyboard interaction
• Mouse interaction
– Does not change the appearance
<div id=”myId" class=”…”>
<button class=”…” id=”…”
role="slider”
aria-labelledby=”sliderLabelID”
aria-valuemin="0" aria-valuemax="100”
aria-valuenow="0" aria-valuetext="0%”>
</button>
</div>
Insert periodic table of ARIA attributes
• ARIA attributes:
– Solve the problem of multiple labels and
descriptions through the addition of finer-
grained labeling attributes
– Enhance the ARIA roles through the addition
of standard state, value and role-specifying
attributes
– Add some attributes for better control of what
is spoken by the screen reader versus what is
simply there for presentational purposes
<div id=”myId" class=”…”>
<button class=”…” id=”…”
role="slider”
aria-labelledby=”sliderLabelID”
aria-valuemin="0" aria-valuemax="100”
aria-valuenow="0" aria-valuetext="0%”>
</button>
</div>
<div id=”myId" class=”…”>
<button class=”…” id=”…”
role="slider”
aria-labelledby=”sliderLabelID”
aria-valuemin="0" aria-
valuemax="100”
aria-valuenow="0" aria-
valuetext="0%”>
</button>
</div>
<div id=”myId" class=”…”>
<button class=”…” id=”…”
role="slider”
aria-labelledby=”sliderLabelID”
aria-valuemin="0" aria-
valuemax="100”
aria-valuenow="0" aria-
valuetext="0%”>
</button>
</div>
<div id=”myId" class=”…”>
<button class=”…” id=”…”
role="slider”
aria-labelledby=”sliderLabelID”
aria-valuemin="0" aria-
valuemax="100”
aria-valuenow="0" aria-
valuetext="0%”>
</button>
</div>
<div id=”myId" class=”…”>
<button class=”…” id=”…”
role="slider”
aria-labelledby=”sliderLabelID”
aria-valuemin="0" aria-
valuemax="100”
aria-valuenow="0" aria-
valuetext="0%”>
</button>
</div>
<div id=”myId" class=”…”>
<button class=”…” id=”…”
role="slider”
aria-labelledby=”sliderLabelID”
aria-valuemin="0" aria-
valuemax="100”
aria-valuenow="0" aria-
valuetext="0%”>
</button>
</div>
handled = false,
$this = jQuery(this);
if (e.ctrlKey || e.shiftKey || e.altKey || e.metaKey) {
// not interested
return;
}
/*
* Open a sub-menu and place focus on the first menuitemwithin it
*/
function openMenu() {
if($this.hasClass("a11yfy-has-submenu")) {
$this.addClass("open").attr("aria-expanded", "true").find(">ul>li:visible").first().attr("tabindex", "0").focus();
$this.attr("tabindex", "-1");
}
}
/*
* Move the focus to the menuitem preceding the current menuitem
*/
function prevInMenu() {
var $context = $this;
$this.attr("tabindex", "-1");
while (true) {
if ($context.prev().is(':visible')) {
$context.prev().attr("tabindex", "0").focus();
return
}
$context = $context.prev();
if (!$context.prev().length) {
$context = $this.parent().find(">li").last();
if ($context.is(':visible')) {
$context.attr("tabindex", "0").focus();
return
}
}
if ($context[0] === $this[0]) {
$this.attr("tabindex", "0")
break;
}
}
}
/*
* Move the focus to the next menuitem after the currently focussed menuitem
*/
function nextInMenu() {
var $context = $this;
$this.attr("tabindex", "-1");
while (true) {
if ($context.next().is(':visible')) {
$context.next().attr("tabindex", "0").focus(
/*
* This implements the WAI-ARIA-PRACTICES keyboard functionality where
* pressing the key, corresponding to the first letter of a VISIBLE element
* will move the focus to the first such element after the currently focussed
* element
*/
var keyCode = e.charCode || e.which || e.keyCode,
keyString = String.fromCharCode(keyCode).toLowerCase(),
ourIndex = -1,
currentItem = this,
$this = jQuery(this),
$nextItem, $prevItem,
$menuitems = $menu.find("li[role=\"menuitem\"]:visible");
if (keyCode === 9) {
return true;
}
$menuitems.each(function(index, value) {
if (value === currentItem) {
ourIndex = index;
}
if (index > ourIndex && !$nextItem) {
if (jQuery(value).text().trim().toLowerCase().indexOf(keyString) === 0) {
if (ourIndex !== -1) {
$nextItem = jQuery(value);
} else if (!$prevItem) {
$prevItem = jQuery(value);
}
}
}
});
if (!$nextItem && $prevItem) {
$nextItem = $prevItem;
}
if ($nextItem) {
$nextItem.attr("tabindex", "0").focus();
$this.attr("tabindex", "-1");
if ($nextItem.parent().get(0) !== $this.parent().get(0)) {
$this.parent().parent("li").removeClass("open").attr("aria-expanded", "false");
}
}
e.stopPropagation();
}).on("keydown", function(e) {
/*
* This implements the WAI-ARIA-PRACTICES keyboard navigation functionality
*/
var keyCode = e.which || e.keyCode,
} else {
/* If in sub-menu, open sub-sub-menu */
openMenu();
}
break;
case 40: //down
handled = true;
if ($this.parent().hasClass("a11yfy-top-level-menu")) {
/* If in menubar, open sub-menu */
openMenu();
} else {
/* If in sub-menu, move to the next menuitem */
nextInMenu();
}
break;
}
if (handled) {
e.preventDefault();
e.stopPropagation();
}
return true;
);return
}$context = $context.next();if (!$context.next().length) {
$context = $this.parent().find(">li").first();if ($context.is(':visible')) {
$context.attr("tabindex", "0").focus();return
}}if ($context[0] === $this[0]) {
$this.attr("tabindex", "0")break;
}}
}switch(keyCode) {
case 32: // spacecase 13: // enter
handled = true;if ($this.find(">a").length) {
if ($this.find(">a")[0].click) {/* If this is a leaf node, activate it*/$this.find(">a")[0].click();
} else {// This is a hack for PhantomJS$this.find(">a").first().trigger("click");
}} else {
/* If it has a sub-menu, open the sub-menu */openMenu();
}break;
case 37: //leftcase 27: //esc
handled = true;if (keyCode === 37 && $this.parent().hasClass("a11yfy-
top-level-menu")) {/* If in the menubar, then simply move to the previous
menuitem */prevInMenu();
} else {if ($this.parent().attr("role") === "menu") {
// this is part of a submenu, set focus on containing li$this.parent().parent().attr("tabindex", "0").focus()
.removeClass("open").attr("aria-expanded", "false");
$this.attr("tabindex", "-1");}
}break;
case 38: //uphandled = true;if ($this.parent().hasClass("a11yfy-top-level-menu")) {
/* If in the menubar, then open the sub-menu */openMenu();
} else {/* If in sub-menu, move to previous element */prevInMenu();
}break;
case 39: //righthandled = true;if ($this.parent().hasClass("a11yfy-top-level-menu")) {
/* If in menubar, move to next menuitem */nextInMenu();
First ARIA Best Practice – If there is a native HTML element that does the
job, use that
Examples
1. Use <button> and <input type=“submit”> NOT <a role=“button”>
2. Use <ul>, <ol> and <li> NOT <span role=“list”> etc.
Compelling ARIA roles
• Landmark Roles– main, search, navigation, contentinfo, complementary,
banner
– region in combination with aria-label
• Live Region Roles– log, status, alert
• Some Widget Roles– tabpanel and tab
– slider
– menu, menubar, menuitem and associated attributes
– dialog – in combination with the document role to get it to work in NVDA
– tree and treeitem – a bit tricky to get to work reliably
• Some form roles– button, textbox, checkbox, radio, radiogroup
• presentation role
Second ARIA Best Practice – test it
on all YOUR platforms with the
assistive technology YOU must
support
All platforms have problems, most
have workarounds, iOS is the
most problematic and Android is
not quite ready for prime time yet
ARIA holes
• Tables, tables, tables
– Use the a11yfy library
• Arrow keys on iOS
– Insert dynamic modal content in line
– Use gestures
• Gestures
– Think hard about your mapping to the
portable gestures
– add on screen controls where possible
Accessible Gesture Calendar Example
https://github.com/dylanb/gestura11y
http://dylanb.github.io/datepicker/datepicker.html
• Shows use of tabindex to control focus
• Shows use of role=“application” to force application mode
• Shows use of aria-live regions to announce the current date as the user moves around
• Shows use of aria-hidden to hide presentation markup from the screen reader
• Shows use of keyboard handler and mapping to gestures
• Shows how to ensure that gestures are consistent regardless of zoom level
Accessible Gesture Calendar Example
https://github.com/dylanb/gestura11y
Third ARIA Best Practice – Always
attach your event handlers to the
same element that has the role
and the focus
If you stick to this rule, you will avoid
events not being delivered
consistently
Fourth ARIA Best Practice – In
complex widgets like menubars,
tabpanels etc. always make all
interim structures presentational
Fifth ARIA Best Practice – in a
complex widget where you are
managing focus, disable all
naturally focusable elements with
tabindex=“-1”
Example is the a11yfy menu
examples where the anchors are
given tabindex=“-1”
Finally
There is a wealth or resources including
The ARIA specification (recommendation, normative)
http://www.w3.org/TR/wai-aria/
The Authoring Practices (draft) http://www.w3.org/TR/wai-aria-practices/
Using ARIA in HTML (draft, informative)
http://www.w3.org/TR/aria-in-html/
The WAI Web Site http://www.w3.org/WAI/intro/aria
Mozilla Developer Network https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA