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 ⌥Ctrlstop talkingVO+▶︎/VO+◀VO+Cmd ⌘(+Shift) +NAuto Web SpotVO+SpacebarClickVO+Cmd ⌘+up/downchange speed Use webVO+Shift+downinteract with website, when browsingVO+Cmd ⌘+HNavigate by headingsVO+UThe Rotor
NVDA
Caps lockNVDA keyCtrl+Alt+Nstart NVDACaps lockstop talkingHnavigate by headingShift+Hnavigate backwards by headingFjump to next fieldCaps lock+Spacebarenter forms modeCaps lockenter Application modeTabmove tabbable elementsSpacebarclick elementCaps lock+F7enter 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 havelangattribute
Headings
<h1>to<h6>accordingly- only one
<h1>per page
Structure & Landmarks
<header><nav><aside><footer><main><article><section>
Buttons and Links
- decide
buttonvs.a aleads somewhere,buttonperforms an action
Inputs
input
Images
altattribute on image
Style
flexandgridproperty 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: noneandvisibility: hiddenremove 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)
SpacebarEnter
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)
tabindexgreater zero focussed first.tabindexstarts 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)
disabledattribute 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:focusin 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"
linkbuttoninput
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: nonearia-hiddentabindex="-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-labelledbyattributearia-labelattribute<label>- Contents (e.g. Button Text)
titleattribute
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"ESCto 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.activeElementc. 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)groupregionstatusbuttoncheckboxradiorateralertmenutreetreeitemdialog
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
alttexts 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");
}