Blog

How Screen Readers Actually Work: From DOM to Speech

TestParty
TestParty
January 29, 2026

Screen readers don't read pixels. They read semantics. When a blind user navigates your website, they're not perceiving your carefully designed visual layout—they're hearing an interpretation of your markup filtered through an accessibility tree, platform APIs, and text-to-speech synthesis. Understanding this pipeline is essential for any developer writing accessible code.

The process works like this: a browser parses your HTML into a DOM, then builds a parallel accessibility tree containing only semantically relevant information—roles, names, states, and relationships. Operating system accessibility APIs expose this tree to assistive technology. The screen reader queries these APIs and presents information to users through speech or braille.

If your markup is wrong, your product is wrong for screen reader users. There's no workaround. A `<div>` styled to look like a button might be visually indistinguishable from a `<button>`, but screen readers expose it differently. Missing labels, improper headings, and broken ARIA all create barriers that no amount of visual polish can fix. According to WebAIM's 2024 Million report, 54.5% of home pages have images with missing alternative text—a problem that makes those images completely invisible to screen reader users.


Key Takeaways

Understanding the screen reader pipeline helps developers write code that actually works with assistive technology.

  • The accessibility tree, not the DOM, is what matters – Browsers create a parallel structure containing only semantic information; screen readers read this, not the raw DOM
  • Native HTML provides the best semantics – A `<button>` is inherently accessible; a `<div onclick>` requires extensive work to replicate that accessibility
  • Accessible names have a computation order – Content, aria-label, aria-labelledby, and label elements combine according to specific rules developers must understand
  • Reading order follows DOM order, not visual order – CSS can reposition elements visually while leaving DOM order unchanged, creating confusion
  • Dynamic updates need explicit handling – SPAs must manage focus and use ARIA live regions because screen readers don't automatically detect content changes

The Mental Model

Picture the flow from your code to a screen reader user's ears. Each step transforms the previous one, and problems at any stage create barriers.

The Pipeline

  1. Developer writes HTML/CSS/JavaScript – This is what you control
  2. Browser parses HTML into DOM – Document Object Model represents the page structure
  3. Browser computes styles and layout – CSS is applied, positions calculated
  4. Browser builds accessibility tree – Semantic information is extracted into a parallel structure
  5. OS accessibility APIs expose the tree – Windows (UIA/MSAA), macOS (NSAccessibility), Linux (ATK)
  6. Screen reader queries accessibility APIs – JAWS, NVDA, VoiceOver, TalkBack request information
  7. Screen reader generates speech/braille – User perceives the final output

Notice that CSS and visual presentation largely drop out after step 3. The accessibility tree doesn't know your button is red or that you used a beautiful font. It knows the button's role, name, and state.

What Enters the Accessibility Tree

Not everything in the DOM appears in the accessibility tree. The browser filters based on semantic relevance:

+--------------------------------------------------+----------------------------------------------------+
|                   DOM Element                    |                 Accessibility Tree                 |
+--------------------------------------------------+----------------------------------------------------+
|            `<button>Submit</button>`             |            Role: button, Name: "Submit"            |
+--------------------------------------------------+----------------------------------------------------+
|     `<img src="photo.jpg" alt="Team photo">`     |          Role: image, Name: "Team photo"           |
+--------------------------------------------------+----------------------------------------------------+
|     `<div style="display:none">Hidden</div>`     |               Not included (hidden)                |
+--------------------------------------------------+----------------------------------------------------+
|   `<span aria-hidden="true">Decorative</span>`   |             Not included (aria-hidden)             |
+--------------------------------------------------+----------------------------------------------------+
|              `<div>Some text</div>`              |            Role: generic, Name: (none)             |
+--------------------------------------------------+----------------------------------------------------+
|             `<h2>Section Title</h2>`             |   Role: heading (level 2), Name: "Section Title"   |
+--------------------------------------------------+----------------------------------------------------+

The accessibility tree is a simplified, semantic view of your content. That's what screen readers work with.


DOM vs. Accessibility Tree

The distinction between DOM and accessibility tree is the single most important concept for accessible development.

The DOM: What You Write

The DOM represents document structure: elements, attributes, and their relationships. It includes everything—semantic elements, generic containers, styles, scripts, everything.

<nav aria-label="Main navigation">
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/products">Products</a></li>
  </ul>
</nav>
<main>
  <h1>Our Products</h1>
  <div class="product-grid">
    <article>...</article>
  </div>
</main>

The Accessibility Tree: What Users Get

The accessibility tree extracts semantic information:

navigation "Main navigation"
  list
    listitem
      link "Home"
    listitem
      link "Products"
main
  heading level 1 "Our Products"
  article

Notice what's preserved (navigation landmark, heading level, link names) and what's dropped (the `<ul>`/`<li>` structure becomes a simple list; the `div.product-grid` disappears entirely).

What Doesn't Appear

Several things remove elements from the accessibility tree:

  • `display: none` – Element is not rendered, not in tree
  • `visibility: hidden` – Element takes space but is hidden from AT
  • `aria-hidden="true"` – Explicitly hidden from accessibility APIs
  • Elements with no semantic meaning – Empty `<div>`s and `<span>`s may collapse

Understanding these rules is essential for techniques like visually hidden text that should be announced but not seen.


Why Native HTML Beats ARIA

The first rule of ARIA is: don't use ARIA. Native HTML elements come with built-in accessibility that ARIA can only approximate.

Native Elements: Free Accessibility

A native `<button>` element provides:

  • Role – Automatically exposed as "button" to screen readers
  • Keyboard operation – Responds to Enter and Space automatically
  • Focus management – Natively focusable without tabindex
  • States – Disabled state exposed when you use the `disabled` attribute
  • Activation – Click handlers fire from keyboard without extra code

All of this comes free. The browser handles it.

ARIA Reconstruction: Expensive and Error-Prone

To make a `<div>` behave as a button, you need:

<div 
  role="button"
  tabindex="0"
  aria-disabled="false"
  onclick="handleClick()"
  onkeydown="handleKeyDown(event)"
>
  Click me
</div>

Plus JavaScript:

function handleKeyDown(event) {
  if (event.key === 'Enter' || event.key === ' ') {
    event.preventDefault();
    handleClick();
  }
}

And you might still miss edge cases: the disabled state styling, the focus ring, form submission behavior, and so on.

When ARIA Is Necessary

Use ARIA when native HTML can't express the semantics you need:

  • Tab panels – No native `<tablist>`, `<tab>`, `<tabpanel>` elements
  • Comboboxes – Complex autocomplete patterns need ARIA
  • Tree views – Hierarchical navigation requires ARIA roles
  • Live regions – Dynamic announcements use `aria-live`
  • Custom widgets – Non-standard interaction patterns

Even then, follow the ARIA Authoring Practices to implement patterns correctly.


Accessible Name Computation

Every interactive element needs an accessible name—the text a screen reader announces when the user focuses the element. Browsers compute this name following a specific algorithm.

The Computation Order

The browser checks these sources in order, using the first one that provides a name:

  1. aria-labelledby – References another element's content by ID
  2. aria-label – Directly specifies the name
  3. Native labeling – `<label for="...">`, `alt` attribute, or element content
  4. Title attribute – Fallback (unreliable; avoid as primary label)
  5. Placeholder – Only for inputs; disappears when typing; poor accessibility

Understanding this order matters when you have conflicting labels.

Common Name Patterns

+----------------------------------------------------+-----------------------------------+
|                      Element                       |        How Name Is Derived        |
+----------------------------------------------------+-----------------------------------+
|             `<button>Submit</button>`              |       Text content: "Submit"      |
+----------------------------------------------------+-----------------------------------+
|      `<button aria-label="Close">X</button>`       |        aria-label: "Close"        |
+----------------------------------------------------+-----------------------------------+
|        `<img src="..." alt="Company logo">`        |   alt attribute: "Company logo"   |
+----------------------------------------------------+-----------------------------------+
| `<input id="email"><label for="email">Email</label>` |     Associated label: "Email"     |
+----------------------------------------------------+-----------------------------------+
|       `<a href="/pricing">See our plans</a>`       |   Text content: "See our plans"   |
+----------------------------------------------------+-----------------------------------+

Common Failures

Icon buttons without names:

<!-- Bad: screen reader says "button" -->
<button><svg>...</svg></button>

<!-- Good: screen reader says "Close menu" -->
<button aria-label="Close menu"><svg>...</svg></button>

Duplicate IDs breaking aria-labelledby:

<!-- Bad: which "title" is referenced? -->
<h2 id="title">First</h2>
<h2 id="title">Second</h2>
<div aria-labelledby="title">...</div>

Labels that exist visually but aren't associated:

<!-- Bad: label isn't programmatically associated -->
<span>Email:</span>
<input type="email">

<!-- Good: label is associated via for/id -->
<label for="email">Email:</label>
<input type="email" id="email">

These failures create elements that screen reader users can reach but can't understand.


Reading Order vs. Visual Order

Screen readers generally follow DOM order. CSS can reposition elements visually while leaving DOM order unchanged. This creates a potential mismatch that confuses users.

The Problem

Consider this layout:

<div class="grid">
  <aside class="sidebar">Navigation</aside>
  <main class="content">Main content</main>
</div>

With CSS Grid:

.grid { display: grid; grid-template-columns: 1fr 3fr; }
.content { grid-column: 1; }  /* Visually first */
.sidebar { grid-column: 2; }  /* Visually second */

Sighted users see main content first, sidebar second. Screen reader users hear sidebar first, main content second—because DOM order hasn't changed.

Best Practices

  • Keep DOM order aligned with visual order for primary flows
  • Use semantic landmarks (main, nav, aside) so users can navigate directly to sections
  • Don't use CSS order extensively for important content sequence
  • Test with screen readers to verify reading order makes sense

Flexbox's `order` property and Grid's placement have the same issue. The DOM is what screen readers traverse.

When Order Differences Are Acceptable

Some cases are fine:

  • Visually hidden content that's read but not seen
  • Mobile-first responsive changes where DOM order matches mobile (primary experience)
  • Decorative reordering that doesn't affect comprehension

The key is testing. If a screen reader user can follow the content logically, the order is acceptable.


Screen reader users navigate differently than sighted users. Understanding these modes helps developers structure content for efficient navigation.

Browse Mode (Virtual Cursor)

In browse mode, users navigate through content using various methods:

  • Arrow keys – Move through content character by character or line by line
  • Heading navigation – Press H to jump to next heading, Shift+H for previous
  • Landmark navigation – Jump directly to `<main>`, `<nav>`, `<footer>`
  • Link list – Pull up a list of all links on the page
  • Form control navigation – Press F to jump between form fields

This is why heading structure matters so much—it's how users scan pages.

Focus Mode (Forms Mode)

When users enter an interactive element (input field, custom widget), the screen reader often switches to focus mode:

  • Standard keyboard goes to the element, not screen reader navigation
  • Arrow keys might control the widget (like selecting from a dropdown)
  • Tab moves to next focusable element

Screen readers switch modes automatically when entering form controls. Custom widgets may need `role="application"` or specific ARIA to trigger appropriate mode switching.

How This Affects Development

  • Use real headings (`<h1>`-`<h6>`) for page structure—users navigate by them
  • Include landmarks (`<main>`, `<nav>`, `<header>`, `<footer>`, `<aside>`)—users jump directly to them
  • Make link text descriptive—users often scan link lists out of context
  • Follow keyboard patterns for custom widgets—users expect standard behaviors

Dynamic Content and Focus Management

Single-page applications and dynamic interfaces introduce challenges screen readers don't automatically handle.

The Problem with SPAs

Traditional page navigation:

  1. User clicks link
  2. Browser loads new page
  3. Screen reader announces page title
  4. Focus resets to top of page

SPA navigation:

  1. User clicks link
  2. JavaScript updates content
  3. URL changes via history API
  4. Screen reader... doesn't know anything changed

Without explicit handling, screen reader users are stuck on the old content, unaware that the page has updated.

Focus Management Solutions

Move focus after navigation:

// After SPA route change
const mainContent = document.querySelector('main');
mainContent.setAttribute('tabindex', '-1');
mainContent.focus();

Announce route changes:

<div aria-live="polite" class="visually-hidden" id="route-announcer"></div>
// After route change
document.getElementById('route-announcer')
  .textContent = 'Navigated to Products page';

ARIA Live Regions

For dynamic content updates that don't involve focus:

<div aria-live="polite" aria-atomic="true">
  <!-- Content changes will be announced -->
  Cart: 3 items
</div>

Live region options:

+---------------------+--------------------------------------------+-------------------------------------------------+
|      Attribute      |                   Values                   |                     Behavior                    |
+---------------------+--------------------------------------------+-------------------------------------------------+
|     `aria-live`     |        `polite`, `assertive`, `off`        |                When to interrupt                |
+---------------------+--------------------------------------------+-------------------------------------------------+
|    `aria-atomic`    |              `true`, `false`               |   Announce entire region or just changed part   |
+---------------------+--------------------------------------------+-------------------------------------------------+
|   `aria-relevant`   |   `additions`, `removals`, `text`, `all`   |             What changes to announce            |
+---------------------+--------------------------------------------+-------------------------------------------------+

Use `polite` for most updates. Reserve `assertive` for critical alerts.

Modal Focus Trapping

When opening a modal:

  1. Move focus into the modal (first focusable element or heading)
  2. Trap focus (Tab cycles within modal; doesn't escape to page behind)
  3. Close on Escape
  4. Return focus to the triggering element when closing

This pattern is documented in ARIA Authoring Practices for dialogs.


Debugging Accessibility

Developers can inspect the accessibility tree to understand what screen readers will perceive.

Browser DevTools

Chrome DevTools:

  1. Open DevTools (F12)
  2. Elements panel → Accessibility pane
  3. Select an element to see its accessible name, role, and properties

Firefox Accessibility Inspector:

  1. Open DevTools
  2. Accessibility tab
  3. Browse the full accessibility tree

These tools show exactly what the browser exposes to assistive technology.

Practical Debugging Workflow

  1. Inspect the accessibility tree – Does the element have the expected role and name?
  2. Check keyboard access – Can you Tab to it? Does Enter/Space activate it?
  3. Test with a screen reader – Quick test with NVDA (Windows) or VoiceOver (Mac)
  4. Run automated checks – axe-core catches common WCAG violations

For the 54.5% of pages with missing alt text (WebAIM data), DevTools will show images with empty accessible names—immediately flagging the problem.

Common Issues to Check

+---------------------+------------------------+---------------------------------------------------+
|    What to Check    |     Where to Look      |              What You're Looking For              |
+---------------------+------------------------+---------------------------------------------------+
|   Accessible name   |   Accessibility pane   |         Name isn't empty; name makes sense        |
+---------------------+------------------------+---------------------------------------------------+
|         Role        |   Accessibility pane   |    Matches element purpose (button, link, etc.)   |
+---------------------+------------------------+---------------------------------------------------+
|     Focus order     |    Tab through page    |              Logical, efficient order             |
+---------------------+------------------------+---------------------------------------------------+
|        States       |   Accessibility pane   |   Expanded/collapsed, checked, disabled exposed   |
+---------------------+------------------------+---------------------------------------------------+
|       Headings      |   Accessibility tree   |        Proper hierarchy, no skipped levels        |
+---------------------+------------------------+---------------------------------------------------+
|      Landmarks      |   Accessibility tree   |         Main, nav, header, footer present         |
+---------------------+------------------------+---------------------------------------------------+

FAQ

Do screen readers actually read the DOM directly?

No. Screen readers read the accessibility tree, which browsers construct from the DOM. The accessibility tree filters out non-semantic information (decorative elements, hidden content) and exposes semantic information (roles, names, states). The DOM is the input; the accessibility tree is the output that screen readers consume.

Why does my button work visually but fail for screen readers?

If you used a `<div>` or `<span>` instead of `<button>`, the accessibility tree exposes it without the button role. You can add `role="button"`, but you also need `tabindex="0"` for focusability and keyboard event handlers for Enter/Space activation. Using native `<button>` solves this automatically. Check the DevTools accessibility pane to see what role is being exposed.

How do I test my site with a screen reader?

Start with VoiceOver on Mac (Cmd+F5 to toggle) or NVDA on Windows (free download). Basic testing: navigate your site using Tab and arrow keys, listening to what's announced. Check that interactive elements are announced with correct names and roles, headings provide structure, and forms can be completed. See Screen Reader Testing Guide for comprehensive methodology.

Does CSS affect what screen readers announce?

Mostly no, but with exceptions. `display: none` and `visibility: hidden` remove elements from the accessibility tree. `aria-hidden="true"` hides content from AT while keeping it visible. CSS can reorder elements visually without changing DOM order, creating reading order confusion. Beyond that, colors, fonts, and positioning don't affect screen reader output—only semantic structure matters.

Why do screen readers sometimes read things in the wrong order?

Screen readers follow DOM order, not visual order. If CSS Grid or Flexbox reposition elements visually, the announced order may differ from what sighted users see. Check your DOM structure and ensure it matches the logical reading sequence. If elements must be visually reordered, test with a screen reader to ensure the experience still makes sense.

Can ARIA fix any accessibility problem?

No. ARIA can add or modify semantics—giving a `<div>` a button role, adding names, indicating states. But ARIA cannot make an element keyboard focusable (you need `tabindex`), cannot add keyboard event handling (you need JavaScript), and cannot fix structural issues like heading hierarchy. ARIA patches semantics; it doesn't replace proper HTML and interaction code.


Internal Links

External Sources


This article was written by TestParty's editorial team with AI assistance. All statistics and claims have been verified against primary sources. Last updated: January 2026.

Stay informed

Accessibility insights delivered
straight to your inbox.

Contact Us

Automate the software work for accessibility compliance, end-to-end.

Empowering businesses with seamless digital accessibility solutions—simple, inclusive, effective.

Book a Demo