Français

Screen readers don't read like you

Table of contents

By leaps, not by lines

Molly Burke lost her sight at fourteen. Diagnosed at four with retinitis pigmentosa, a rare genetic disease that progressively destroys the retina, she saw colors disappear at thirteen, then the entire world go dark within months. Today, she's followed by over 2 million people on YouTube, where she shows among other things how she uses her iPhone with VoiceOver.

Watch one of her videos. What you'll hear will probably surprise you: a synthesized voice rattling off at a speed you won't understand. Molly swipes on her screen, and VoiceOver announces fragments. An app name, a button, a message. Not continuous sentences. She doesn't "read" her phone. She navigates it, in leaps, at a speed her eyes couldn't follow even if they worked.

And that's exactly what screen readers do on the web. They don't read your page top to bottom, word by word, like you're reading this article. They slice it up, structure it, and offer the user shortcuts to jump directly to what they're looking for. Just like you when you visually scan a page to find the right paragraph.

If you don't understand how they work, you risk building pages that seem accessible in the code but are unusable in practice.

The tape recorder that isn't one

The most common image of a screen reader is a tape recorder: a tool that reads everything, from start to finish, in order. That's wrong.

The WebAIM 2024 survey asked 1,539 screen reader users about their navigation habits. Result: 71.6% navigate by headings when looking for information on a page. Not by reading everything. By jumping from <h1> to <h2> to <h3>, just like you when you scan an article's subheadings to decide which one to read.

And that's not the only strategy. Advanced users, 76% of those who call themselves "experts", combine headings with other shortcuts. Each screen reader offers its own commands to navigate by element type. In NVDA, the most used in Europe, free and open source, the H key jumps to the next heading, T to the next table, F to the next form field. In VoiceOver on macOS, the rotor (Ctrl + Option + U) displays a filtered list by type: headings, links, landmarks, form controls. The keys differ, the principle is the same: the page is a map, and the user jumps from landmark to landmark.

But these landmarks only exist if your HTML declares them. A heading that's just a <div> styled in bold? The screen reader doesn't see it. The user presses H, or opens the rotor, and nothing appears. The map is empty. All that's left is to listen to the entire page from start to finish. Exactly the tape recorder you imagined, but by default of structure, not by choice.

Three modes, one keyboard

When you navigate a web page, your keyboard does one thing: send keystrokes to the browser. When a screen reader user navigates the same page, their keyboard does three different things depending on context, and switches between them automatically.

Browse mode

This is the default mode. The screen reader intercepts keystrokes before they reach the browser and transforms them into navigation commands. H doesn't type the letter H, it jumps to the next heading. Tab doesn't move to the next field, it advances to the next focusable element. The user explores a linear copy of the page, called the virtual buffer, that the screen reader built from the DOM.

This is the mode where element-type shortcuts work, and this is the mode where your HTML structure matters most.

Forms mode

When the user lands on an <input>, a <select> or a <textarea>, the screen reader switches automatically: it stops intercepting keystrokes and lets them through to the browser. H now types the letter H. Tab moves to the next field. The user has left the map to interact with the page.

This switch is seamless when the HTML is semantic. An <input> triggers forms mode. A <div contenteditable>, not always. A <div role="textbox">, depends on the screen reader. Once again, native HTML is the safest path.

Application mode

The third mode is the most dangerous. When an element carries role="application", the screen reader disables all its navigation shortcuts and passes full keyboard control to the page's script. No more H for headings, no more single-letter shortcuts: only Tab keeps moving between focusable elements, everything else depends on JavaScript.

This mode exists for complex widgets that handle their own keyboard interaction. A spreadsheet, a code editor, a game. But if you put it on a container that doesn't implement complete keyboard behavior, the user ends up trapped in an empty shell: no screen reader shortcuts, no shortcuts from your script. Léonie Watson explains it in detail: putting role="application" without implementing all expected keyboard behavior is like removing the steering wheel from a moving car.

From HTML to the ear

When you write HTML, you're probably thinking about visual rendering. The browser, however, builds three things in parallel:

  1. Visual rendering: what you see on screen
  2. The DOM: the tree of HTML elements that JavaScript can manipulate
  3. The accessibility tree: a simplified version of the DOM, stripped of decoration, keeping only what makes sense for assistive technologies

The accessibility tree is what the screen reader reads. Not the DOM. Not the CSS. Not the visual rendering. A <div> with font-weight: bold and font-size: 24px? In the accessibility tree, it's a generic element with no role and no semantics. It doesn't exist as a heading. An <h2> with no styling? It's a level 2 heading, the screen reader announces it, and the user can jump to it with the navigation shortcut.

You can inspect the accessibility tree of any page in your browser's developer tools, Accessibility tab. It's the most revealing view of what your HTML actually communicates — and what it forgets to say.

The AOM: manipulating the tree from JavaScript

The DOM has its JavaScript API, document.querySelector, element.setAttribute. The accessibility tree didn't have one. The only way to modify what a screen reader saw of an element was to place ARIA attributes in the HTML. aria-label, role, aria-expanded. It works, but it pollutes the DOM with attributes that have nothing to do with document structure.

The Accessibility Object Model (AOM) is an API under development, led by Apple, Google and Mozilla, that aims to give direct JavaScript access to the accessibility tree. Its first phase, reflecting ARIA properties as JavaScript properties on DOM elements, is already supported in Chromium, Firefox and Safari, with varying levels of coverage.

aom-reflection.js
// Before the AOM: HTML attribute
button.setAttribute("aria-expanded", "true");
 
// With the AOM: reflected property (writes the same attribute)
button.ariaExpanded = "true";

The difference is thinner than it looks: the property is a reflection of the attribute. button.ariaExpanded = "true" writes aria-expanded="true" into the DOM, exactly like setAttribute. The gain is ergonomic (a typed, discoverable property, no attribute-name strings to spell out), not structural. To define a component's semantics without touching the generated HTML, it's ElementInternals, the AOM's second building block, that does the job.

The AOM goes further with ElementInternals, which lets custom elements declare their default semantics. A <my-button> can behave like a <button> for the screen reader without any visible ARIA attribute. Later phases, queries on the computed accessibility tree, virtual nodes, are still in development, but the direction is clear. The accessibility tree is becoming a first-class citizen of the web platform, on par with the DOM and visual rendering.

A landscape, not a tool

There isn't "the" screen reader. There's an ecosystem, with market shares, different behaviors, and users who often combine several.

The WebAIM 2024 survey gives a clear picture:

On desktop:
  • JAWS: 40.5% (leader in North America, paid: perpetual license around $1,000, or an annual subscription under $100)
  • NVDA: 37.7% (leader in Europe and Asia, free and open source)
  • VoiceOver: 9.7% (built into macOS, free)
  • Narrator: 0.7% as primary reader (built into Windows, 37.3% of respondents use it occasionally)
On mobile:
  • VoiceOver (iOS): 70.6%
  • TalkBack (Android): 34.7%

Two numbers to remember: 91.3% of respondents use a screen reader on mobile. Mobile web isn't secondary, it's primary. And 65.6% use NVDA overall (not just as their primary reader). It's the most widespread if you count all usage.

What matters to you: JAWS and NVDA share the same virtual buffer concept and several navigation shortcuts. H for headings, T for tables, F for form fields, but not all of them. NVDA uses D for landmarks where JAWS uses R. NVDA has a K key to jump to the next link, and both distinguish unvisited links (U) from visited ones (V). VoiceOver works differently still, rotor instead of single-letter shortcuts, gesture navigation on iOS. The exact shortcuts change, the principle stays the same. The page is a map, and each screen reader offers its own commands to traverse it.

That's why testing on a single screen reader isn't enough, and it's also why native HTML is the safest path. A <button> works correctly on every screen reader. A <div role="button"> works differently on each.

The 5-minute test

You don't need to master a screen reader to verify your page is navigable. But you do need to use one. The accessibility tree in developer tools shows structure, not experience. It doesn't tell you if focus moves correctly, if the switch between browse mode and forms mode works, or if the voice announcement makes sense in the reading flow. It's a diagnostic tool, not a substitute for real testing.

VoiceOver on macOS

  1. Cmd + F5: activate VoiceOver
  2. Ctrl + Option + U: open the rotor
  3. Left/Right arrows: switch between lists of headings, links, landmarks, form controls
  4. Up/Down arrows: navigate within the selected list
  5. Enter: jump to the chosen element
  6. Cmd + F5: deactivate VoiceOver

What you're checking: does the heading list reflect your page structure? Do all your links have a label that's understandable out of context? Do your landmarks (<nav>, <main>, <aside>) appear?

NVDA on Windows

NVDA is free. Install it, open your page, and:

  1. Insert + F7: open the elements list (headings, links, landmarks)
  2. H: next heading
  3. D: next landmark
  4. Tab: next focusable element
  5. Insert + Space: toggle between browse mode and forms mode

The accessibility tree in the browser

If you can't install a screen reader, the accessibility tree in developer tools (F12, Accessibility tab) is still useful for a first diagnosis. You'll see the role, accessible name and states of each element. But keep in mind it's an X-ray, not a clinical exam. It shows anatomy, not function. An element can have the right role in the tree and still be unusable via keyboard, or be announced incomprehensibly in the reading flow.

The overall criteria

WCAG covers the structure and navigation that enable screen readers to function:

All Level A and AA, the legal minimum under the EAA in Europe, the ADA in the US, and the Equality Act in the UK.

These criteria guarantee that the page's "map" exists, that headings are hierarchical, that regions are identified, and that users can navigate efficiently instead of enduring a linear stream.

Sources