import highlight from '@mdx-deck/themes/syntax-highlighter-prism'
import Slide from "../templates/slide" import ImageSlide from "../templates/image-slide" import CodeBlock from "../components/site-chrome/code-figure" import AccessibleAnimationDemo from "../components/better/animation" import Autocomplete from "../components/better/downshift-autocomplete"
import baseTheme from '../theme' export const themes = [baseTheme, highlight] const openModal = () => { document.querySelector('.modal-curtain').removeAttribute('hidden') } const buttonAction = () => { alert('Button clicked') }
@marcysutton ~ marcysutton.com
Making the web more inclusive
with and for people with disabilities
http://bit.ly/microsoft-inclusive-toolkit
- Client-rendered: no traditional page reloads
- Built with frameworks—React, Vue, Ember, Angular, etc.
- Sometimes server-rendered with “hydration”
- Challenges and opportunities
- Accessibility debugging
- Accessibility in JavaScript apps
- Focus management
- Client-side routing
- Announcements
- Semantic HTML
- Unobtrusive motion
- Progressive enhancement
- Accessibility units
- Accessible pages
- Q & A
- Built with Gatsby and React
- Server and client-rendered
- Outputs HTML pages by default
- Includes this slide deck
https://github.com/marcysutton/js-a11y-workshop
git clone https://github.com/marcysutton/js-a11y-workshop.git
cd js-a11y-workshop
npm install
npm run develop
View the site locally: http://localhost:8000
Online: https://marcysutton.github.io/js-a11y-workshop
⚒
- NVDA (Windows)
- axe extensions
- Accessibility Insights
- more
Prototyping with Parcel
Modern development with any JavaScript framework/library
git clone https://github.com/marcysutton/parcel-prototype-scaffold parcel-demo
cd parcel-demo
npm install
npm start
Alternatively: Codepen, Codesandbox, etc.
- React
- Ember
- Angular
- Polymer
- Vue
- Svelte
- Vanilla
- Render in a web browser
- Test controls with the keyboard
- Use accessibility web extensions
- Check color contrast
- Test with screen readers
- Use magnification & zoom
Fire up a build in a browser and start testing
$ npm run build
$ npm run serve
http://localhost:9000
Hidden vs. Visible CSS
.visually-hidden {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.opacity {
opacity: 0;
}
.displayNone {
display: none;
}
.visibility {
visibility: hidden;
}
Uses platform Accessibility APIs to communicate page
structure & content to assistive technologies
Semantic markup and CSS styles impact the accessibility tree
document.body.addEventListener('focusin', (event) => {
console.log(document.activeElement)
})
Cheat sheets for getting started
- https://webaim.org/articles/voiceover/
- https://webaim.org/articles/nvda/
- https://webaim.org/articles/jaws/
- OSX Voiceover and Safari
- NVDA and Firefox Windows
- JAWS and IE11 or Edge
- iOS Voiceover and Safari
- Android Accessibility and Chrome
- Orca on Linux
- Browser zoom (all the way: 500%!)
- OS-level zoom
- ZoomText & other assistive tech
Things to watch out for:
- Page scrolling
- Font sizes & scaling
- UX of interactions when zoomed
- Focus management
- Live Region announcements
- Semantic HTML
- Unobtrusive motion
- Progressive enhancement
Moving the user’s focus as part of an interaction
to alert them to new content
Also: handling focus in disabled and mutated parts of the page
- Reachable and operable elements
- TAB, escape, and arrow keys
- Visible focus styles
- Hidden/inert content
Make non-interactive elements focusable
tabIndex="0" // in the tab order. see following slides
tabIndex="-1" // focusable by script, or removes from tab order
tabIndex="99641" // all up in your tab order. hard to manage
Expose accessibility information for focusable elements.
<div tabIndex="0"
role="button"
aria-label="Close">
</div>
// focusable
// a button widget, not a DIV
// an accessible name
💡 Intended for custom interactive elements, not wrapper DIVs
Make custom controls fully interactive
<div tabIndex="0"
role="button"
aria-label="Close"
onClick={clickHandler}
onKeyDown={keydownHandler}>
</div>
// or just use a button :)
<button aria-label="Close"
onClick={clickHandler}>
</button>
https://www.w3.org/TR/wai-aria-1.1/
- role: what is it?
- state: what's happening to it?
- property: what's the nature of it?
The first rule of ARIA is don’t use it
aria-hidden="true"
andtabindex="-1"
inert
(+ polyfill)- CSS
display: none
- Dropdowns and menus
- Layers and modals
- View changes and deletes
- Loading screens
It depends on the role and pattern ✨
http://w3c.github.io/aria-practices/
<a href="/page">Page</a>
<button onClick={action}>Thing</button>
https://marcysutton.com/links-vs-buttons-in-modern-web-applications
Useful for so many people and situations, but often turned off for everyone
*:focus {
outline: none;
}
:hover, :focus {
/* interaction styles here */
outline: 5px auto blue;
}
:focus:not(.focus-visible) {
outline: 0;
}
:focus-visible {
outline: 3px solid blue;
}
// in a layout or component
import 'what-input'
/* in your CSS */
[data-whatintent='mouse'] *:focus {
outline: none;
}
An OS-level preference would be ideal 👍
Cached class names aren't user friendly, e.g. class="sc-bdVaJa"
import React from 'react'
import { styled } from 'styled-components'
const MegaHeader = styled.header`
background-color: rebeccapurple;
`
const Layout = () => (
// add a stable CSS class
<MegaHeader className={`site-wrap`}>
<h1>My Gatsby Site</h1>
</MegaHeader>
)
http://localhost:8000/dropdown
- React component shell: dropdown.js
- vanilla.js shell: https://codepen.io/marcysutton/pen/aeJdNq
- React: dropdown component
- vanilla.js: https://codepen.io/marcysutton/pen/JgjYVv
Navigation where JavaScript controls browser history
and dynamically maps URLs to each page or view
- A small UI control in each view target, like a skip link
- Label with nearby content and its action,
e.g. "Portfolio, skip to navigation" - When a user clicks a nav link, move focus to this control
- Access DOM nodes with React refs
- New: React FocusScopes
- Vue.js $refs
- Ember suggestions
- Other framework APIs?
- Custom focus manager APIs
- Gatsby
onRouteUpdate
method and skip link focus - vanilla.js shell: https://codepen.io/marcysutton/pen/wVJeJQ
- Gatsby component: examples/client-side-routing
- vanilla.js: https://codepen.io/marcysutton/pen/MNpmMd
Notify assistive tech users without moving focus
- Asynchronous save / update / etc.
- Combobox usage / list filtering
- Chat widgets
- Title changes*
Message command centers of varying importance
https://www.w3.org/TR/wai-aria/#live_region_roles
<div role="status"></div>
<div role="alert"></div>
<div aria-live="polite"></div>
<div aria-live="assertive"></div>
- Include multiple regions for stubborn situations
- Politeness levels depend on the use case
- Site-level announcement manager APIs 👍
https://github.com/AlmeroSteyn/react-aria-live
http://localhost:8000/announcer
- React.js shell: live-region
- vanilla.js shell: https://codepen.io/marcysutton/pen/xvqdzx
- React component: examples/live-region
- vanilla.js: https://codepen.io/marcysutton/pen/ZgeKaV
- Use headings & landmarks
- Start wih native UI controls
- Build semantics into templates
- Verify assistive tech output
Headings save time, and programattic information is useful
Semantic HTML communicates what's on a page to users of assistive technology, reader modes, conversational UIs, search engines, and more
- Accessibility Insights: Headings
- Firefox Web Developer Extension:
Information > View Document Outline - NVDA heading navigation, elements list
- Voiceover rotor
- Browser reader modes
Add markup to a blank page and test it using Accessibility Insights.
- Gatsby page shell: pages/semantics.jsx
Building safe and delightful interfaces
prefers-reduced-motion
CSS- media, animation playback controls
- opt-in for autoplay
https://source.opennews.org/articles/motion-sick/
https://codepen.io/marcysutton/pen/yqVVeY
@media (prefers-reduced-motion: reduce) {
.animation {
animation: none;
transition: none;
}
}
var motionQuery = matchMedia('(prefers-reduced-motion)');
function handleReduceMotionChanged() {
if (motionQuery.matches) {
/* adjust 'transition' or 'animation' properties */
} else {
/* standard motion */
}
}
motionQuery.addListener(handleReduceMotionChanged);
handleReduceMotionChanged(); // trigger once on load
http://localhost:8000/animation
Play with reduced-motion and CSS animation or transitions
- React component shell: card-flip.js
- vanilla.js shell: https://codepen.io/marcysutton/pen/OKppJe
- React: examples/reduced-motion
- vanilla.js: https://codepen.io/marcysutton/pen/yqVVeY
Emphasize core web page content first, then add layers of presentation and features on top as browsers/network connections allow
- Turn off JavaScript
- Provide accessible baseline markup
- Add ARIA with scripting
- Prioritize core user flows
Output static HTML by default at build time
gatsby build
http://localhost:8000/enhanced/tablist
Play with progressive enhancement
- React component shell: components/tab-list.js
- vanilla.js starter: https://codepen.io/marcysutton/pen/WBvRxq
- React component: examples/tablist.js
- vanilla.js: https://codepen.io/marcysutton/pen/oKBzdr
Improving quality in development
- linting
- unit tests
- integration/end-to-end
- accessibility test APIs
- continuous integration
- manual & user testing
Testing for quality live in a file or on a commit
- you might need to reconfigure rules
- CSS can have an impact
- rendering makes a difference
🧩
- Test code in isolation
- Stub inputs/fixture data
- Often headless
- Fast changing of state
- Component-specific behavior
- Interaction/focus APIs
- Text alternatives
- ARIA states
Article: Writing Automated tests for Accessibility
- Dropdown with Jest: _tests_
Write an accessibility unit test!
- Jest test shell: dropdown.test.js
- Jasmine shell: https://codepen.io/marcysutton/pen/OKpVKZ?editors=1010
- https://testing-library.com/docs/dom-testing-library
- https://github.com/testing-library/jest-dom
- Jest test: dropdown.test.js
- Jasmine test: Modal dialog
📰
- Real-world browser testing
- Document/page-level rules
- Widget interrop
- Color contrast
- Framework flexibility
const WebDriver = require('selenium-webdriver')
describe('Keyboard tests', () => {
let driver = new selenium.Builder().forBrowser('chrome').build()
driver.get('http://localhost:4000').then(() => done())
it('should change state with the keyboard', () => {
var selector = 'span[role="radio"][aria-labelledby="radiogroup-0-label-0"]';
driver.findElement(selenium.By.css(selector))
.then((element) => {
element.sendKeys(Key.SPACE)
return element
})
.then((element) => element.getAttribute('aria-checked'))
.then((attr) => {
expect(attr).toEqual('true')
})
})
})
/// <reference types="Cypress" />
describe("Accessibility checks", () => {
beforeEach(() => {
cy.visit("/")
cy.wait(500)
})
it("Checks if footer link is focusable and has the correct attributes", () => {
cy.getAllByText("Gatsby").focus()
cy.focused()
.should("have.text", "Gatsby")
.should("have.attr", "href", "https://www.gatsbyjs.org")
.should("not.have.css", "outline-width", "0px")
})
})
Tests can catch roughly 30-50% of accessibility issues,
depending on the rule set
👉 Screen readers can’t be automated
Write an end-to-end accessibility test for
keyboard interactions and/or with an accessibility test API
- Cypress test shell: index.js
- Cypress API docs: https://cypress.io
- Cypress tests: examples/integration-testing
Opportunities to test for accessibility on every commit,
pull request, and deployment
https://marcysutton.github.io/a11y-and-ci/
https://twitter.com/zqxwzq/status/868039653697482753
Automated testing and linting only gets us so far.
Projects need manual human testing, too
🙇♀️
From Eric Bailey
https://www.smashingmagazine.com/2018/09/importance-manual-accessibility-testing/
https://inclusivedesign24.org/2019/