Accessibility – A11y

Overview

Types of disabilities

  • motor
  • cognitive
  • auditory
  • visual

Sources

Cool accessible sites


Screen Readers

Voice Over (Mac)

  • Cmd ⌘+F5 – Turn on Voice Over
  • VO – Modifier Key: Ctrl+Alt ⌥
  • Ctrl stop talking
  • VO + ▶︎ / VO +
  • VO + Cmd ⌘ (+ Shift) + N Auto Web Spot
  • VO + Spacebar Click
  • VO + Cmd ⌘ + up / down change speed Use web
  • VO + Shift + down interact with website, when browsing
  • VO + Cmd ⌘ + H Navigate by headings
  • VO + U The Rotor

NVDA

  • Caps lock NVDA key
  • Ctrl + Alt + N start NVDA
  • Caps lock stop talking
  • H navigate by heading
  • Shift + H navigate backwards by heading
  • F jump to next field
  • Caps lock + Spacebar enter forms mode
  • Caps lock enter Application mode
  • Tab move tabbable elements
  • Spacebar click element
  • Caps lock + F7 enter Elements mode

Google Chrome

(Automated) Testing

  • aXe Core
  • Selenium for automated testing

Semantic HTML

HTML

  • <html> should have lang attribute

Headings

  • <h1> to <h6> accordingly
  • only one <h1> per page

Structure & Landmarks

  • <header>
  • <nav>
  • <aside>
  • <footer>
  • <main>
  • <article>
  • <section>
  • decide button vs. a
  • a leads somewhere, button performs an action

Inputs

  • input

Images

  • alt attribute on image

Style

  • flex and grid 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 and visibility: 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

No Tabindex
Tabindex 0
Tabindex -1
Tabindex 5

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

Div

Disabled Div

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 have tabindex="-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


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


Alert

Options

  • tabindex="-1" and .focus() element with JavaScript
  • role="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 attribute
  • aria-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>

  • role="dialog"
  • ESC to exit dialog
  1. 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; }
  1. when opening the dialog a. make dialog control focussable (tabindex="0") b. save the focussed element at the time of firing up the modal via document.activeElement c. focus some element (e.g. the first) into the dialog d. only dialog controls are tabbable nothing else in the document.

  2. when closing the dialog revert changed from 2a., 2c. and 2d.

  3. 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 information
  • aria-labelledby – provides name via different object
  • aria-describedby – provides extended information

ARIA Current

used to indicate current item in a set

  • aria-current="page" – current link
  • aria-current="step" – current step
  • aria-current="location" – current image of flowchart
  • aria-current="date" – current date on calendar
  • aria-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");
}