How to not Fail at Building Accessible Web Applications
By Joy HeronThis site is also available as a slideshow.
Get to know your users
- 👩🏻🦯Users with visual impairments
- 😩Users with cognitive limitations
- 🧏🏿Users with auditory impairments
- 🎨Users with different color perceptions
- 🏎️Users with epilepsy or motion sickness
- ⌨️Users with physical impairments
- 📱Users who cannot afford expensive hardware
Web Content Accessibility Guidelines (WGAG) 2.2 Principles
Begin by building and testing the HTML layer
Consider multiple possible semantic HTML layers which would work as a base
Document your thoughts as you go!
It may be necessary to add minimal amounts of JavaScript to test any widgets that we want to add.
Example: HTML-only Navbar
<details>
elements are a great HTML-only fallback!
Add skip links to improve navigation for keyboard users
Add a few choice ARIA attributes to improve the accessibility of your markup
- Add a descriptive
aria-label
to eachul
unordered list - Indicate link to current page with
aria-current="page"
- Add
role="list"
to all lists where we are going to change thedisplay
property with CSS (See Blog Post on "Fixing Lists" by Scott O'Hara)
What are all of the points that we need to consider to make the HTML of a website accessible?
Add a CSS layer to implement the design
Style skip links so that they are hidden except when focused
.skip-link {
/* ...styles for skip link... */
&:not(:focus) {
@extend %sr-only;
}
}
Connect CSS rules to ARIA attributes to ensure HTML is correct
/* Use CSS rules to enforce the accessibility of the current link
within the navbar. If it is visually correct, it will also be
semantically correct. */
a[aria-current='page'] {
text-decoration: underline;
text-decoration-color: $active-color;
text-decoration-thickness: 3px;
text-underline-offset: $spacer-xs;
}
See Blog Post on "Using CSS to Enforce Accessibility" by Adrian Roselli
Add rule only removing list styles when we have added a role
attribute to a list
/* Removing `list-style` attributes causes the list to no longer
appear as a list in the accessibility tree. As a workaround, this
only removes the list styling once the ARIA `role="list"` has
been set to reinstate the list semantics for the list. */
ul[role='list'],
ol[role='list'] {
list-style: none;
list-style-type: none;
}
Define CSS properties to tweak the theme based on CSS media features
@media (prefers-reduced-motion: no-preference) {
--grid-row-transition: grid-template-rows #{$transition-speed} #{$transition-easing};
--visibility-transition: visibility #{$transition-speed} #{$transition-easing};
}
@media (forced-colors: active) {
--forced-colors-link-border-width: 0;
--forced-colors-action-margin: #{$navbar-focus-outline-width};
--forced-colors-link-decoration: underline;
--forced-colors-navbar-toggler-width: 10rem;
}
@media (pointer: fine) {
--pointer-fine-navbar-meta-font-size: #{$small-font-size};
--pointer-fine-navbar-meta-spacer: #{$spacer-xs * 0.5};
}
Use defined CSS properties instead of hard coded values
&[data-expanded] {
grid-template-rows: var(--main-navigation-grid-template-rows);
transition: var(--grid-row-transition);
.navigation-links,
.navigation-meta {
overflow: hidden;
min-height: 0;
transition: var(--visibility-transition);
visibility: var(--main-navigation-menu-visibility);
}
}
Note that it is finally possible to animate height: auto;
by animating the grid-template-rows
property!
(See Blog Post on "Animating height: auto" by Nelson Menezes)
Important Media Queries
-
prefers-reduced-motion
— ALWAYS reduce motion when a user requests it to prevent possible epileptic event or motion sickness 🏎️ -
forced-colors
,prefers-contrast
, andprefers-color-scheme
indicate different user color needs 🎨 -
pointer
andhover
can indicate usage of mobile devices 📱
Use a color contrast checker to ensure that all color combinations pass at least the WCAG 2.0 AA level
Focus Styles with sufficient contrast!
With focus-visible
we can turn off focus rings for users who are navigating with a mouse.
/* Remove outline styles when the user is not navigating with
the keyboard */
&:focus:not(:focus-visible) {
border-color: transparent;
outline: none;
}
Example: Navbar with only HTML and CSS
Add JavaScript to improve the design
Progressively-enhance <details>
elements to become a toggle button using aria-expanded
export default class Submenu extends HTMLElement {
connectedCallback() {
let summary = this.querySelector('summary')
let ul = this.querySelector('ul')
if (!summary && !ul) {
return
}
// With our method of animating the collapsing/expanding of
// the menu, it is necessary to wrap the content of the menu
// in a `<div>`
this.innerHTML = `<button type="button" aria-expanded="false">${summary.innerHTML}</button><div>${ul.outerHTML}</div>`
this.button.addEventListener('click', () => this.toggle())
this.navigation.addEventListener('submenu-toggle', (ev) => {
if (ev.detail && ev.detail.expanded &&
ev.target !== this.button) {
if (this.button.getAttribute('aria-expanded') === 'true') {
this.toggle(false)
}
}
})
this.toggle(false)
}
toggle(expanded = !(this.button.getAttribute('aria-expanded') === 'true')) {
let button = this.button
button.setAttribute('aria-expanded', expanded)
button.dispatchEvent(new CustomEvent('submenu-toggle', { detail: { expanded }, bubbles: true }))
}
get navigation() {
return this.closest('ul')
}
get button() {
return this.querySelector('button')
}
get div() {
return this.querySelector('div')
}
get ul() {
return this.querySelector('ul')
}
}
customElements.define('sub-menu', Submenu);
Hook CSS styles for showing/hiding the menu to the aria-expanded
attribute
This ensure that the attribute is correctly defined on the button
sub-menu {
div {
display: grid;
overflow: hidden;
grid-template-rows: var(--submenu-grid-template-rows);
transition: var(--grid-row-transition);
}
ul {
min-height: 0;
margin-bottom: $spacer-xs;
transition: var(--visibility-transition);
visibility: var(--submenu-visibility);
}
button[aria-expanded='false'] + div {
--submenu-grid-template-rows: 0fr;
--submenu-visibility: hidden;
}
button[aria-expanded='true'] + div {
--submenu-grid-template-rows: 1fr;
--submenu-visibility: visible;
}
}
Example: Navbar with HTML, CSS and JavaScript
When else may I need JavaScript?
If something changes on page, it is necessary to announce this to the user
-
Create an HTML element with
role="status"
andaria-live="polite"
and update it to announce changes for assistive technologies. - it's usually necessary for the element to already exist before its content is changed in order for its content to be announced
- 🔥 Tip: integrate area for announcements into the visual design to help all users, not only users of assistive technology
Know your widgets
Widget | Pay attention to... |
---|---|
Disclosure Widget |
aria-expanded is an attribute on the button, the expanded content should directly
follow the button that expands it
|
Tabbed Interfaces | It shouldn't look like tabs unless it behaves like tabs. Tab selection should occur by arrow keys not the TAB-key. (Most tab components don't do this correctly) |
Dialogs |
Need to implement focus trapping and make sure ESC-key shuts the dialog
(usually the user also wants to close it by clicking outside of the dialog).
It's also critical that there is a focussable close button within the dialog.
Use <dialog> element.
|
Listboxes |
Options in list should be able to be selected with arrow keys
(prefer using a built-in <select> )
|
First rule of ARIA
If you can use a native HTML element or attribute with the semantics and behavior 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.
Accessibility Evaluation Tooling
Use tooling like the WAVE web accessibility evaluation tool or Accessibility Insights to evaluate your application!
How to test your web application
For a user with... | ...test with... |
---|---|
visual impairments 👩🏻🦯 | screenreaders |
cognitive limitations 😩 | 200% zoom, user tests |
auditory impairments 🧏🏿 |
sound turned off (ensure transcripts, closed captions available) |
different color perceptions 🎨 | color contrast checkers, color blindness simulators |
epilepsy or motion sickness 🏎️ |
prefers-reduced-motion: reduce activated!
|
physical impairments ⌨️ | keyboards |
inexpensive hardware 📱 | cheap mobile devices |