Accessibility Basics
Keyboard, focus, labels, contrast — mostly free if you start right, expensive to retrofit.
40 minDifficulty 2/5a11y · craftAI-writtenWritten by a model on 16 August 2026 and not yet read by a person. Checked automatically: schema, the pedagogical rules the hand-written material is held to, and every diagram parsed for real.
Before this
Why this exists
Replacing standard HTML tags with generic `<div>` and `<span>` elements destroys native browser affordances for keyboards, screen readers, and switch devices. Retrofitting accessibility late requires rewriting event loops, managing manual `tabindex` states, and re-architecting forms under compliance pressure. Building accessible interfaces from day one costs almost nothing when leveraging native semantics, clear labels, and sensible focus management.
The mental model
Think of the DOM as a document's visual blueprint and the **Accessibility Tree (A11y Tree)** as its audio script. Native semantic tags like `<button>` and `<nav>` write this script automatically with built-in roles, keyboard activations, and focus states. Generic `<div>` elements leave the audio script blank unless you manually script every interaction, state transition, and descriptor.
How it works
Semantic HTML Provides Free Affordances
Browsers automatically grant interactive semantic elements—like `<button>`, `<a>`, and `<input>`—native keyboard focusability (`Tab`), expected trigger behavior (`Enter` and `Space`), and accessibility roles. Replacing a button with `<div onClick={...}>` strips these defaults, requiring manual `tabIndex={0}`, `role="button"`, and keydown event listeners to emulate what standard HTML already provides for free.
Visual and Programmatic Labels Must Match
Every interactive control requires an accessible name derived from text content, an explicit `<label for="...">`, or an `aria-label`. Icon-only buttons (such as a trash can or hamburger menu) lack visible text, so screen readers announce them as ambiguous 'button' elements unless labeled via `aria-label="Delete item"` or a visually hidden span.
Visible Focus and the Focus Ring
Keyboard users rely entirely on the visual focus indicator to know which element currently receives input. Removing focus indicators using `outline: none` or `outline: 0` without providing a distinct replacement renders the application unusable for keyboard navigation. Always provide high-contrast `:focus-visible` styles that activate exclusively on keyboard interaction.
Color Contrast and Non-Color State Indicators
WCAG 2.1 AA requires a minimum contrast ratio of 4.5:1 for standard body text and 3:1 for large text (18pt+ or 14pt bold) and active UI components. Crucially, state changes (such as form validation errors or selected tabs) must never rely on color alone; they require accompanying icons, text indicators, or semantic `aria-invalid` attributes.
The mechanism
When the browser parses HTML, it constructs the DOM and CSSOM, computing a parallel data structure called the **Accessibility Tree**. This tree exposes semantic roles, states (`aria-expanded`, `aria-checked`), and accessible names to OS-level assistive APIs. When user focus changes, the browser updates the active node in the Accessibility Tree, allowing screen readers to immediately synthesize speech or drive braille displays.
graph TD HTML[HTML & CSS] --> DOM[DOM & CSSOM] DOM --> A11yTree[Accessibility Tree] A11yTree --> PlatformAPI[OS Accessibility APIs] PlatformAPI --> ScreenReader[Screen Reader / Assistive Tech] Keyboard[Keyboard Input] -->|Tab / Enter| FocusEngine[Browser Focus Engine] FocusEngine -->|Active Element| DOM FocusEngine -->|Focus Event| A11yTree
What people get wrong
- Adding ARIA attributes makes any custom component instantly accessible.
- ARIA only changes how an element is announced in the Accessibility Tree; it adds zero keyboard event handling, focus trapping, or browser behavior. Developers often add role='button' to a div and assume it behaves like a button, but it still fails to respond to the Enter or Space keys and remains unfocusable without manual script intervention.
- tabindex="5" or higher helps guide users through a complex page layout.
- Positive tabindex values disrupt natural tab order and create confusing, hard-to-maintain focus jumps; stick strictly to tabindex="0" or tabindex="-1". Positive tabindex values jump ahead of all default elements on the entire page, breaking the natural reading order across modular and dynamically mounted components.
- Accessibility only benefits users who are completely blind.
- Accessibility features benefit keyboard users, people with motor impairments, low-vision users, people in high-glare environments, and power users navigating via shortcuts. Accessible design patterns like visible focus, proper contrast, and keyboard control improve usability for situational, temporary, and permanent physical limitations alike.
When not to use it
- Building custom, complex interactive controls like dynamic comboboxes, tree views, or modals.
- Use headless UI libraries like Radix UI, React Aria, or Ariakit that manage focus trapping, keyboard navigation, and ARIA attributes automatically.
- Hiding decorative images, icons, or duplicate background graphics from screen readers.
- Apply aria-hidden="true" or provide an empty alt attribute (alt="") rather than removing elements from the visual layout.
Terms
- Accessibility Tree
- — A browser-generated tree structure based on the DOM that exposes roles, names, and states to assistive technologies.
- Accessible Name
- — The programmatic text label that assistive technologies use to identify and announce an interactive element to the user.
- Focus Trapping
- — The practice of constraining keyboard focus within a specific modal or dialog so Tab navigation cannot escape to background content.
- tabindex="0" vs tabindex="-1"
- — tabindex="0" inserts an element into natural keyboard tab order; tabindex="-1" makes an element focusable only via JavaScript without placing it in the tab sequence.
Can you recall it?
What happens under the hood when you replace a native <button> with a <div> containing an onClick handler, and what three things must you manually supply to restore accessibility?