web3icons

Tools for testing accessibility

What automated checkers catch and miss, how to test with a keyboard and a screen reader, and which contrast and zoom checks to add to each release.

6 min read

No single tool can tell you a page is accessible. Automated checkers find one class of problems quickly and reliably. Everything else, from whether focus order makes sense to whether a swap confirmation is ever announced, takes a person with a keyboard, a screen reader and a contrast checker.

None of this needs a dedicated lab. Most of the tools are free or built into your operating system, and a routine that covers the main flows of a dApp is small enough to repeat before every release.

Automated checkers

Most browser-based checkers run a rules engine over the rendered page and report failures. A widely used engine is axe-core, which is open source and powers the accessibility audit in Lighthouse (in Chrome DevTools) as well as the axe DevTools extension. Other widely used options are WAVE, a browser extension that marks issues directly on the page, and Accessibility Insights for Web, which adds a guided manual assessment on top of its automated checks.

They are good at checks that can be decided from the markup and computed styles, and blind to anything that depends on meaning or behavior:

Reliably caught Needs a person
Images without an alt attribute Whether the alt text is accurate and useful
Buttons and links with no accessible name Whether a name like "icon" actually describes the button
Form fields with no label Whether the focus order follows the layout
Low contrast for plain text on solid backgrounds Contrast of text over gradients and images, often reported as "needs review"
Invalid ARIA roles and attributes Whether focus is visible at every step
Missing page language Keyboard traps and missing key handlers
Whether live regions announce at the right time

A Lighthouse score of 100 means no automated rule failed. It doesn't mean the page is usable without a mouse. A wallet button named "button" passes the "has a name" rule. A connect-wallet modal that never moves focus passes too.

Automated checks are most valuable when they run on every change. With Playwright, the axe-core integration is a few lines:

import { test, expect } from '@playwright/test'
import AxeBuilder from '@axe-core/playwright'

test('swap page has no detectable violations', async ({ page }) => {
  await page.goto('/swap')
  const results = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
    .analyze()
  expect(results.violations).toEqual([])
})

Check which rule tags your installed axe-core version supports, since newer versions add tags for newer WCAG releases. Run the scan again after opening menus and modals, because content that isn't rendered isn't checked.

The accessibility tree

Between automated checks and a full screen reader session sits a quick step: inspecting the accessibility tree. In Chrome and Edge DevTools, select an element and open the Accessibility pane to see its computed role and name. Firefox has a dedicated Accessibility Inspector.

This is the fastest way to answer "what will a screen reader call this?" For an icon-only copy button, you should see role button and a name like "Copy address", not an empty name and not "svg". For a token row, you can confirm that the logo is ignored and the token name comes through.

Keyboard testing

Put the mouse aside and go through a real task, such as connecting a wallet and making a swap, using only the keyboard:

  • Tab and Shift+Tab reach every interactive element, in an order that follows the layout (2.1.1 Keyboard, A; 2.4.3 Focus Order, A).
  • A focus indicator is visible on every stop (2.4.7 Focus Visible, AA), and it isn't hidden under a sticky header or cookie banner (2.4.11 Focus Not Obscured (Minimum), AA).
  • Enter and Space activate buttons; arrow keys work inside menus, tabs and grids.
  • Escape closes dialogs and popovers, and focus returns to whatever opened them.
  • Nothing traps focus without a way out (2.1.2 No Keyboard Trap, A).

Whenever focus disappears, check document.activeElement in the console. It often turns out to be body, which means an element was removed or disabled while it had focus.

Screen readers

Screen reader Platform Cost Common browser pairing
VoiceOver macOS, iOS, iPadOS built in Safari
NVDA Windows free, open source Chrome, Firefox, Edge
JAWS Windows commercial Chrome, Edge
Narrator Windows built in Edge
TalkBack Android built in Chrome

Test with at least one desktop and one mobile pairing. Behavior differs between them, sometimes a lot, especially for live regions and custom widgets.

A minimal set of commands gets you started:

  • VoiceOver on macOS: Command+F5 turns it on and off. The VO keys are Control+Option. VO+Right Arrow moves to the next item, and VO+U opens the rotor with lists of headings, links and form controls.
  • NVDA: the NVDA key is Insert, or Caps Lock in the laptop layout. Down Arrow reads the next line, H jumps to the next heading, Tab moves between controls, and NVDA+Space switches between browse and focus mode. Control silences speech.
  • TalkBack and VoiceOver on iOS: swipe right or left to move between items, and double-tap to activate.

NVDA's Speech Viewer, under Tools in the NVDA menu, shows everything NVDA says as text. It's handy for checking announcements. Announcing transaction status describes what you should expect to hear.

A tip for crypto flows: run tests with a test wallet on a testnet. You can't fix the wallet extension's own interface, but you can check what your dApp says while it waits for the wallet, and whether focus is sensible when the wallet window closes.

Contrast checkers

For text, Chrome and Edge DevTools show the contrast ratio in the color picker when you click a text color swatch in the Styles pane, with markers for the AA and AAA thresholds. Firefox's Accessibility Inspector can check contrast for a selected element. Desktop apps with an eyedropper, such as the Colour Contrast Analyser, let you sample any pixel on screen, which is what you need for text on gradients, images and translucent overlays.

Remember the thresholds: 4.5:1 for normal text and 3:1 for large text under 1.4.3 Contrast (Minimum) (AA), and 3:1 for interface components, their states such as focus, and meaningful graphics under 1.4.11 Non-text Contrast (AA). Logos are exempt from both, but the focus ring around a logo button is not. Color contrast ratio explained covers how the numbers are calculated.

Zoom, reflow and text spacing

Two layout checks round things out:

Reflow. 1.4.10 Reflow (AA) asks that content work at a width of 320 CSS pixels without scrolling in two directions, with exceptions for content such as data tables and charts that needs a two-dimensional layout. Set the browser window to 1280 px wide and zoom to 400%, or use responsive mode at 320 px. Watch for layouts that push the whole page sideways (a wide token table can scroll inside its own container instead) and for modals whose buttons slide out of view.

Text spacing. 1.4.12 Text Spacing (AA) asks that nothing breaks when users increase spacing. Apply this stylesheet with a bookmarklet or DevTools and look for clipped labels:

* {
  line-height: 1.5 !important;
  letter-spacing: 0.12em !important;
  word-spacing: 0.16em !important;
}
p {
  margin-bottom: 2em !important;
}

Fixed-height buttons and pill-shaped badges with tickers inside are common casualties.

A routine that sticks

Automated scans in CI on every change. An accessibility tree check whenever you add a new control. A keyboard pass and a screen reader pass on the main flows before each release. Contrast and reflow checks whenever colors or layouts change. If you're mapping these checks to requirements, what is WCAG explains how the criteria are organized.

More in Accessibility

All guides