Skip to main content
Technology & EngineeringAccessibility138 lines

Screen Reader Compat

Building web content that works correctly with screen readers like NVDA, JAWS, and VoiceOver

Quick Summary35 lines
You are an expert in screen reader compatibility for building accessible web applications.

## Key Points

- **Accessible name**: Computed from content, `aria-label`, `aria-labelledby`, or the `<label>` element.
- **Accessible description**: Supplementary detail from `aria-describedby`.
- **Role**: Derived from the HTML element or overridden with the `role` attribute.
- **State/properties**: Conveyed through ARIA attributes like `aria-expanded`, `aria-checked`.
- **NVDA + Firefox** is the most common free testing combination on Windows.
- **VoiceOver + Safari** is the standard for macOS and iOS testing.
- Open the browser's accessibility inspector (Chrome DevTools > Accessibility pane) to review the computed accessible name and role of any element.
- Read through every interactive flow with the screen reader: navigation, form submission, error handling, and dynamic content updates.
- Verify that `aria-live` regions announce changes at the right moment and with the right urgency.
- Write semantic HTML first; the correct element (button, nav, main) communicates role without ARIA.
- Test with a real screen reader, not just automated tools—automated tools cannot detect whether the reading order is logical.
- Keep `aria-live` region usage minimal; excessive announcements overwhelm users.

## Quick Example

```html
<button>
  <svg aria-hidden="true"><!-- icon --></svg>
  <span class="sr-only">Close dialog</span>
</button>
```

```html
<!-- Decorative image: empty alt removes from tree -->
<img src="swoosh.svg" alt="">

<!-- Decorative icon: aria-hidden removes from tree -->
<span aria-hidden="true" class="icon-decorative"></span>
```
skilldb get accessibility-skills/Screen Reader CompatFull skill: 138 lines
Paste into your CLAUDE.md or agent config

Screen Reader Compatibility — Web Accessibility

You are an expert in screen reader compatibility for building accessible web applications.

Core Philosophy

Overview

Screen readers convert on-screen content into speech or braille output for users who are blind or have low vision. Building compatible content means using semantic HTML, providing meaningful text alternatives, managing dynamic updates, and testing with the actual assistive technologies users rely on.

Core Concepts

How screen readers parse a page

Screen readers build an accessibility tree from the DOM. Each node in the tree has a role (e.g., button, heading, link), a name (the accessible label), a state (e.g., expanded, checked), and a value. The screen reader navigates this tree, not the visual layout.

Major screen readers

Screen ReaderPlatformBrowser Pairing
JAWSWindowsChrome, Edge
NVDAWindowsFirefox, Chrome
VoiceOvermacOS/iOSSafari
TalkBackAndroidChrome

Accessibility tree fundamentals

  • Accessible name: Computed from content, aria-label, aria-labelledby, or the <label> element.
  • Accessible description: Supplementary detail from aria-describedby.
  • Role: Derived from the HTML element or overridden with the role attribute.
  • State/properties: Conveyed through ARIA attributes like aria-expanded, aria-checked.

Implementation Patterns

Visually hidden text for screen-reader-only content

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
<button>
  <svg aria-hidden="true"><!-- icon --></svg>
  <span class="sr-only">Close dialog</span>
</button>

Live regions for dynamic content

<!-- Polite: announced after current speech finishes -->
<div aria-live="polite" aria-atomic="true">
  3 search results found.
</div>

<!-- Assertive: interrupts current speech -->
<div role="alert">
  Your session will expire in 2 minutes.
</div>

<!-- Status: implicit aria-live="polite" -->
<div role="status">
  File uploaded successfully.
</div>

Meaningful link and button text

<!-- Bad: ambiguous out of context -->
<a href="/report">Click here</a>

<!-- Good: descriptive -->
<a href="/report">Download the Q3 accessibility audit report (PDF, 2.4 MB)</a>

<!-- Acceptable: augmented with sr-only text -->
<a href="/report">
  Download report
  <span class="sr-only">: Q3 accessibility audit, PDF, 2.4 MB</span>
</a>

Hiding decorative content from the accessibility tree

<!-- Decorative image: empty alt removes from tree -->
<img src="swoosh.svg" alt="">

<!-- Decorative icon: aria-hidden removes from tree -->
<span aria-hidden="true" class="icon-decorative"></span>

Testing & Validation

  • NVDA + Firefox is the most common free testing combination on Windows.
  • VoiceOver + Safari is the standard for macOS and iOS testing.
  • Open the browser's accessibility inspector (Chrome DevTools > Accessibility pane) to review the computed accessible name and role of any element.
  • Read through every interactive flow with the screen reader: navigation, form submission, error handling, and dynamic content updates.
  • Verify that aria-live regions announce changes at the right moment and with the right urgency.

Best Practices

  • Write semantic HTML first; the correct element (button, nav, main) communicates role without ARIA.
  • Test with a real screen reader, not just automated tools—automated tools cannot detect whether the reading order is logical.
  • Keep aria-live region usage minimal; excessive announcements overwhelm users.

Common Pitfalls

  • Using aria-label on non-interactive elements (like a <div>) where many screen readers ignore it; use aria-labelledby or visible text instead.
  • Placing aria-hidden="true" on a parent that contains focusable children, creating "ghost" focus traps.

Anti-Patterns

Over-engineering for hypothetical scale. Building for millions of users when you have hundreds adds complexity without value. Solve today's problems first.

Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide well wastes time and introduces unnecessary risk.

Premature abstraction. Creating elaborate frameworks and utilities before you have enough concrete cases to know what the abstraction should look like produces the wrong abstraction.

Neglecting error handling at boundaries. Internal code can trust its inputs, but system boundaries (user input, APIs, file I/O) require defensive validation.

Skipping documentation for obvious code. What is obvious to you today will not be obvious to your colleague next month or to you next year.

Install this skill directly: skilldb add accessibility-skills

Get CLI access →