IAnswerable

What a Screen Reader Actually Reads

04 Nov 2026

Turn on VoiceOver (Cmd+F5 on Mac) or NVDA (free, Windows) and browse your own site for five minutes. You will change how you write markup permanently, because you’ll hear exactly what you’ve been shipping.

What it reads, in order

A screen reader doesn’t read your page the way it looks. It reads the accessibility tree, built from the DOM plus ARIA, and it reads it linearly — top to bottom, ignoring your CSS layout entirely. A three-column layout that’s visually left-to-right but coded right-to-left in the HTML gets read right-to-left. This alone is worth checking: does your DOM order match your reading order, independent of how Grid or Flexbox visually reorders things with order or grid-template-areas.

Elements announce their role and state

This is the part that reframes everything. A native button gets announced as “Menu, button” — role included, automatically. A div with a click handler gets announced as… nothing. Just the text, if any, with no indication it’s interactive at all.

<!-- VoiceOver says: "Submit, button" -->
<button>Submit</button>

<!-- VoiceOver says: "Submit" — no role, sounds like static text -->
<div onclick="submit()">Submit</div>

Same goes for state. A native input type="checkbox" announces “checked” or “unchecked” and toggles correctly with Space. A styled div faking a checkbox announces nothing unless you’ve manually wired role="checkbox" and aria-checked, and even then you have to keep that attribute in sync with actual state by hand, every time it changes.

Images: alt text is a sentence, not a filename

<!-- Screen reader says: "photo, image" — useless -->
<img src="chart.png" alt="photo">

<!-- Says: "Bar chart showing signups up 40% in Q3, image" -->
<img src="chart.png" alt="Bar chart showing signups up 40% in Q3">

<!-- Purely decorative — screen reader skips it entirely, which is correct -->
<img src="divider.svg" alt="">

An empty alt="" is not a mistake — it’s the correct choice for decorative images, and it’s different from no alt attribute at all, which some screen readers fall back to reading the filename for.

Headings are a navigation menu, not a font-size shortcut

Every major screen reader has a shortcut to jump between headings only (H, in NVDA and VoiceOver’s rotor). Users skim a page this way before deciding whether to read it — the same way sighted users skim visually. If your headings skip from h1 to h4 because that happened to match a font size you wanted, that navigation menu is now broken and confusing, independent of how anything looks.

<!-- Wrong: picked for font size, not structure -->
<h1>Page title</h1>
<h4>Section</h4>

<!-- Right: nesting reflects actual document structure -->
<h1>Page title</h1>
<h2>Section</h2>

Style the h4 to look small with CSS if you need to. Don’t use the wrong heading level to get there.

Forms: the error message has to be connected, not just nearby

Visually, putting red text under a field is enough. A screen reader user tabbing through the form won’t hear it unless it’s programmatically associated:

<label for="email">Email</label>
<input type="email" id="email" aria-describedby="email-error" aria-invalid="true">
<span id="email-error">Enter a valid email address</span>

aria-describedby is what makes the connection — without it, the error text is just another paragraph somewhere on the page, disconnected from the field it’s about.

The habit that sticks

After actually hearing your own site read aloud, you stop reaching for div onclick and reach for button by reflex. That single habit change fixes more real accessibility bugs than any linter rule you could add.