Accessibility – A11y
Overview
Types of disabilities
- motor
- cognitive
- auditory
- visual
Sources
Cool accessible sites
Screen Readers
Voice Over (Mac)
Cmd ⌘
+F5
– Turn on Voice OverVO
– Modifier Key:Ctrl
+Alt ⌥
Ctrl
stop talkingVO
+▶︎
/VO
+◀
VO
+Cmd ⌘
(+Shift
) +N
Auto Web SpotVO
+Spacebar
ClickVO
+Cmd ⌘
+up
/down
change speed Use webVO
+Shift
+down
interact with website, when browsingVO
+Cmd ⌘
+H
Navigate by headingsVO
+U
The Rotor
NVDA
Caps lock
NVDA keyCtrl
+Alt
+N
start NVDACaps lock
stop talkingH
navigate by headingShift
+H
navigate backwards by headingF
jump to next fieldCaps lock
+Spacebar
enter forms modeCaps lock
enter Application modeTab
move tabbable elementsSpacebar
click elementCaps lock
+F7
enter Elements mode
Google Chrome
- Accessibility tab
- aXe Web Accessibility Testing – Colour and contrast plugin
- Accessibility Developer Tools – Colour suggestions if contrast is not met
(Automated) Testing
- aXe Core
- Selenium for automated testing
Semantic HTML
HTML
<html>
should havelang
attribute
Headings
<h1>
to<h6>
accordingly- only one
<h1>
per page
Structure & Landmarks
<header>
<nav>
<aside>
<footer>
<main>
<article>
<section>
Buttons and Links
- decide
button
vs.a
a
leads somewhere,button
performs an action
Inputs
input
Images
alt
attribute on image
Style
flex
andgrid
property should not change logical order
Semantic Properties
- Role – e.g.
button
,checkbox
- Name / Label –
aria-labelledby
,aria-label
,<label>
, Contents (e.g. Button Text),title
- State – e.g.
disabled
,checked
- Value – e.g.
input
's text content
Browser generates an accessibility tree from these properties.
CSS
- The accessibility tree is generated from the DOM, therefore CSS styling should reflect the DOM order and/or vice versa.
display: none
andvisibility: hidden
remove elements from the accessibility tree.
Focus
refers to selecting an element (and directing all the subsequent keyboard events to that element)
Keyboard Navigation
Move through document with Keyboard
Tab
(forwards)Shift
+Tab
(backwards)- Arrow buttons or letters for
<select>
Element focussed (synthetic click activation)
Spacebar
Enter
Interactive Elements (Buttons etc.) should be focussable
Tab Order
HTML dictates tab order
implicit for interactive controls
<input>
<select>
<button>
Tab order should reflect document order
tabindex
for custom interactive controls
indicates that its element can be focussed in sequential order
tabindex="0"
- focussable via keyboard in DOM order
.focus()
requires a tabindex attribute for e.g. divs (or focusable element)
tabindex="-1"
- not focussable via keyboard
- focussed programmatically (via
focus()
method in JavaScript)
document.querySelector('[tabindex="-1"]').focus();
tabindex="5"
- focussable via keyboard; jumps ahead of DOM order (avoid)
tabindex
greater zero focussed first.tabindex
starts at lowest value greater than zero an counts up
A screenreader navigates DOM linearly, not with respect to tab order.
Native Buttons
Features of <button>
vs. <div>
etc.
- automatically in taborder
- automatic ARIA role of "Button"
- synthetic click activation (spacebar or enter key)
disabled
attribute function
<button class="button">Button</button>
<div class="button">Div</div>
<button class="button" disabled>Disabled Button</button>
<div class="button" disabled>Disabled Div</div>
Focus in SPA
Strategy
- call
.focus()
on newly loaded content (has to havetabindex="-1"
– probably also overwrite:focus
in CSS)
Colour and Contrast
Minimum contrast
- text and images at least
> 4.5
- large text (> 14-18pt)
> 3.0
Enhanced contrast
- text and images at least
> 7.0
- large text (> 14-18pt)
> 4.5
Tools
- OATMEAL
- Color contrast ratio checker
- Color Contrast Analyzer (Chrome extension) for contrast ratio between image background colour and text foreground colour
Tabindex
Elements with intrinsic tabindex="0"
link
button
input
Programmatically set tab-index="-1"
to take element out of tab order.
Deque website --> skip to content
Example: Custom Element with roving tab index
Remove interactive controls (Inert) from taborder
display: none
aria-hidden
tabindex="-1"
Alert
Options
tabindex="-1"
and.focus()
element with JavaScriptrole="alert"
role="alert"
creates ARIA Live region with aria-live="assertive"
Example
Labels
An elements gets its name for assistive technologies from one of the following:
aria-labelledby
attributearia-label
attribute<label>
- Contents (e.g. Button Text)
title
attribute
Example: <label>
- can be used for
<button>
,<input>
,<select>
wrapped in <label>
using for
-attribute
Example: aria-label
- works for HTML elements like
<button>
,<input>
,<select>
Example: aria-labelledby
Memory Card
Example: Label Shadow-DOM
<custom-element label="faviourite-button"></custom-element>
class CustomElement extends HTMLElement {
static get observeredAttributes() {
return ['label'];
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.button = document.createElement('button');
this.button.textContent = "💚";
this.shadowRoot.appendChild(this.button);
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === "label" && newValue) this.button.setAttribute("aria-label", newValue);
}
}
window.customElements.define("custom-element", CustomElement);
Example: Label Custom Element
<howto-label>
Element
<howto-input></howto-input>
</howto-label>
Modal Dialogs
role="dialog"
ESC
to exit dialog
- make dialog a top-level sibling (direct child of )
<div class="dialog" role="dialog" aria-labelledby="dialog-window-title">
<div class="dialog-window">
<h2 id="dialog-window-title">Dialog title</h2>
<p>Dialog text lorem ipsum dolor est. Sic ut anemone.</p>
<button>Cancel</button>
<button>OK</button>
</div>
<div class="dialog-mask"></div>
</div>
.dialog { display: none; }
.dialog.opened { display: block; }
-
when opening the dialog a. make dialog control focussable (
tabindex="0"
) b. save the focussed element at the time of firing up the modal viadocument.activeElement
c. focus some element (e.g. the first) into the dialog d. only dialog controls are tabbable nothing else in the document. -
when closing the dialog revert changed from 2a., 2c. and 2d.
-
go back to previous element from 2b.
Source
ARIA – Accessible Rich Internet Applications
or WAI ARIA – Web Accessibility Initiative
Resources
ARIA Roles
<span role="rater">★★★★★</span>
Role
switch
(e.g. toggle button)group
region
status
button
checkbox
radio
rater
alert
menu
tree
treeitem
dialog
ARIA Labels
aria-label
– provides essential informationaria-labelledby
– provides name via different objectaria-describedby
– provides extended information
ARIA Current
used to indicate current item in a set
aria-current="page"
– current linkaria-current="step"
– current steparia-current="location"
– current image of flowchartaria-current="date"
– current date on calendararia-current="time"
– current time in timetable
ARIA ActiveDescendent
aria-activedescendent
ARIA Properties vs. states
- properties – relatively static
- states – more dynamic
aria-multiline
aria-singleline
ARIA States
-
aria-checked="true"
-
aria-expanded="true"
-
aria-expanded="false"
-
aria-controls="sect-id"
-
aria-haspopup="menu"
ARIA Live Region
used to announce non-interactive content changes
aria-live="assertive" – update announcement interrups user flow
aria-live="polite" – updates announcement when user is idle
aria-live="off" – turned off
role="alert"
=aria-live="assertive"
role="status"
=aria-live="polite"
Example: aria-expanded
and aria-controls
<button aria-expanded="false" aria-controls="settings-panel">Settings</button>
<div id="settings-panel">
<label>Option 1 <input type="checkbox" checked></label>
<label>Option 2 <input type="checkbox"></label>
</div>
button[aria-expanded="false"] + #settings-panel {
display: none;
}
button[aria-expanded="true"] + #settings-panel {
display: block;
}
Example: aria-labelledby
<p id="input-label">This is an input field</p>
<div aria-labelledby="input" contenteditable="true"></div>
<p id="item1">First item</p>
<p id="item2">Second item</p>
<div role="group" aria-labelledby="item1">
<a href="javascript:" role="button" aria-labelledby="item1">Item Content</a>
</div>
Example: aria-label
<div aria-label="This is an input field"></div>
Example: aria-activedescendent
<radio-group role="radio-group" tabindex="0" aria-activedescendent="rb3">
<radio-button id="rb1" role="radio" aria-checked="false">Water</radio-button>
<radio-button id="rb2" role="radio" aria-checked="false">Tea</radio-button>
<radio-button id="rb3" role="radio" aria-checked="true">Matcha</radio-button>
</radio-group>
Images
- Always add
alt
texts to<img>
-tags!
<img width="1200" height="900"
loading="lazy"
src="butterfly.png"
alt="a beautiful butterfly">
Colour and Contrast
Focus Ring
CSS Level-4 selector
- like
:focus
, but only for non-mouse users
:focus-visible {
}
:not(:focus-visible) {
outline: none;
}
Reduced Motion
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
const isSafeToAnimate = window.matchMedia("(prefers-reduced-motion: no-preference)").matches;
if (isSafeToAnimate) {
// do animation
}
Dark Mode
@media (prefers-color-scheme: dark) {
body {
background-color: #000;
color: white;
}
}
@media (prefers-color-scheme: light) {
body {
background-color: #FFF;
color: black;
}
}
@media (prefers-color-scheme: no-preference) {
}
const prefersDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
if (prefersDarkMode) {
document.querySelector("body").classList.add("dark");
}