Accessibility is a core engineering concern

Building inclusive sites means accounting for the full range of human ability. At least six key areas of disability deserve your attention: visual, hearing, mobility, cognition, speech, and neural. An accessible page works across devices with varying screen sizes and input methods—including screen readers—and remains usable by the broadest possible group of users, including those with disabilities. Over one billion people live with some form of disability.

Visual impairments

Visual issues range from an inability to distinguish colors to total blindness. To serve these users:

  • Ensure text content meets a minimum contrast ratio threshold.
  • Never communicate information using color alone, and verify all text is resizable.
  • Make every UI component operable by assistive technologies such as screen readers, magnifiers, and braille displays. That requires markup such that accessibility APIs can programmatically determine the role, state, value, and title of each element.

Note: Many developers live with low vision and rely on zoom features in browsers and tools. Supporting zoom is rarely a top priority, but it makes a genuine difference to a large user base.

Hearing impairments

Users with hearing issues may miss audio cues entirely. Provide text alternatives for all non-text content, and test that your UI remains functional without sound.

Mobility impairments

Mobility issues can make it impossible to use a mouse, keyboard, or touchscreen normally. Make all UI component functionality accessible from a keyboard, and ensure pages are correctly marked up for assistive technologies—which rely on the same APIs used by screen readers, voice control, and physical switch controls.

Cognitive and neural considerations

Users with cognitive impairments may need text alternatives to help them read. Beyond that, be mindful of motion: avoid video and animation that repeat or flash, which can cause issues for some users. The prefers-reduced-motion CSS media query lets you limit animations and autoplaying videos:

/*
If the user expresses a preference for reduced motion, don't use animations on buttons.
*/
@media (prefers-reduced-motion: reduce) {
  button {
    animation: none;
  }
}

Also avoid interactions that are timing-based.

Auditing your UI components

When assessing a component for accessibility, work through these questions:

  • Can you use it with the keyboard only? The component should manage focus, avoid focus traps, and respond to the appropriate keyboard events.
  • Can you use it with a screen reader? Provide text alternatives for visual information and add semantic information using ARIA.
  • Can it work without sound? Turn off your speakers and walk through each use case.
  • Can it work without color? Test with simulations like the Colorblindly Chrome extension (all four forms of color blindness) or the Daltonize extension.
  • Can it work with high-contrast mode enabled? Modern operating systems support this; the High Contrast Chrome extension helps too.

Standard controls such as <button> and <select> have accessibility built into the browser. They are focusable with the Tab key, respond to keyboard events like Enter, Space, and arrow keys, and expose semantic roles and states to accessibility tools. Their default styling meets standard accessibility requirements.

Custom UI components—except those extending standard elements—have none of these built-in capabilities. You must provide accessibility yourself. A solid starting point is to model a custom component after an analogous standard element, or a combination of several.

All major browser developer tools let you inspect the accessibility tree. In Chrome DevTools, use the Accessibility tab in the Elements panel. Firefox has an Accessibility panel, and Safari exposes the same information in the Elements panel's Node tab.

Keyboard focus first

The foundation of accessibility is keyboard operability. Before implementing, decide how you'd use the component with only a keyboard and design a consistent interaction model. Ensure each component has a sensible focus target. A complex component like a menu may be a single focus target in the page, but it must manage focus internally so the active item always holds it.

The three faces of tabindex

The tabindex attribute governs keyboard focus. Built-in interactive elements are implicitly focusable and need no tabindex unless you want to shift their tab position.

  • tabindex="0": places the element in natural tab order (DOM order).
  • tabindex="-1": makes the element programmatically focusable, but removes it from the tab sequence.
  • A positive tabindex: establishes a manual tab order. Positive values are visited numerically, before the natural tab order.

For use cases, see the article Using tabindex. Always keep focus visible—using the default ring or a custom style—and never trap keyboard users.

Autofocus

The HTML autofocus attribute works on all form controls, including buttons. For custom components, call the focus() method on load, e.g. document.querySelector('myButton').focus().

Meaningful keyboard interaction

With focusability handled, add keyboard event handling. Let arrow keys select options in a menu; let Space or Enter activate buttons. The ARIA design patterns guide offers structure for this. Make shortcuts discoverable—a simple on-screen hint like "Press ? for keyboard shortcuts" is often enough.

Focus management is especially crucial for components like navigation drawers. If you drop a new UI component on the page, move focus to an element inside it; otherwise, users must tab through the entire page before reaching it. Test focus behavior on every keyboard-navigable component.

// Example for expanding and collapsing a category with the Space key
const category = await page.$(`.category`);

// verify tabIndex, role and focus
expect(await page.evaluate(elem => elem.getAttribute(`role`), category)).toEqual(`button`);
expect(await page.evaluate(elem => elem.getAttribute(`tabindex`), category)).toEqual(`0`);
expect(await page.evaluate(elem => window.document.activeElement === elem, category)).toEqual(true);

// verify aria-expanded = false
expect(await page.evaluate(elem => elem.getAttribute(`aria-expanded`), category)).toEqual(`false`);

// toggle category by pressing Space
await page.keyboard.press('Space');

// verify aria-expanded = true
expect(await page.evaluate(elem => elem.getAttribute(`aria-expanded`), category)).toEqual(`true`);

Screen reader essentials

Screen readers are used by roughly 1%–2% of people. Verify that all important information and functionality can be accessed through a screen reader and keyboard alone.

Provide text alternatives

Whenever a component’s name or purpose is conveyed visually, expose an accessible text alternative. For example, a custom <fancy-menu> that displays only a gear icon needs a text alternative such as “settings.” Depending on the context, use an alt attribute, aria-label, aria-labelledby, or plain text in the Shadow DOM. Components that display images should offer a mechanism for providing alternative text analogous to the alt attribute. See the WebAIM Quick Reference for technical details.

Add semantics to custom components

Assistive technology relies on semantic information that browsers build into standard elements. Custom components must expose this information through ARIA.

Any component that handles mouse clicks or hover events should also include a keyboard event listener and an appropriate ARIA role, with states and attributes as needed. A custom <fancy-slider>, for instance, should take the ARIA role slider and bind aria-valuenow, aria-valuemin, and aria-valuemax to its underlying properties. This allows assistive technology users to change the value and update the visual presentation.

A screenshot of a slider.
A range slider component.

Don’t rely on color alone

Color should never be the only signal for status, prompts, or data visualization. A pie chart, for example, needs labels and values for each slice so the information is clear without distinguishing slice boundaries by color.

A pie chart with labels and values to ensure accessibility.
An accessible pie chart. (From the W3C Web Accessibility Initiative.)

Check contrast and motion

Text must meet the WCAG AA contrast threshold. Consider providing a theme that reaches the AAA level, and allow user agent style sheets to override colors where needed. A Color Contrast Checker can help during design.

Content that moves, scrolls, or blinks for more than five seconds must be pausable, stoppable, or hideable. Avoid flashing content entirely if possible; when it is necessary, keep it to no more than three flashes per second.

Testing and tooling

More than 100 tools exist for evaluating accessibility, ranging from automated checks to manual testing.

Screenshot of the Lighthouse accessibility audit. Screenshot of a code editor with an accessibility issue flagged by eslint-plugin-jsx-a11y. Screenshot of a code editor with an accessibility issue flagged by codelyzer.

Check with real assistive technology

To see how assistive tech interprets your content, use Accessibility Inspector on Mac, or the Windows Automation API Testing Tools and AccProbe on Windows. Chrome exposes its full accessibility tree at about://accessibility.

  • On Mac, VoiceOver is the standard test utility: toggle with ⌘F5, navigate with Ctrl+Option ←→, and move through the accessibility tree with Ctrl+Shift+Option + ↑↓. The full command list and Web command list are available.
  • On Windows, NVDA is a free, open source option, though it has a steep learning curve for sighted users.
  • ChromeOS includes a built-in screen reader.

Accessibility gaps persist across the web, per the Web Almanac:

  • 4 out of 5 sites have text that blends into the background.
  • 49.91% of pages miss alt attributes on some images.
  • Only 24% of pages using buttons or links include labels.
  • Just 22.33% of pages label all form inputs.

There is substantial room for improvement in building accessible experiences.