Designing for screen readers is less about adding a few ARIA attributes at the end and more about understanding how someone actually moves through a page when they can't see it. Screen reader users rarely read top to bottom. They jump by heading, skim landmarks, pull up a list of links, and tab through interactive controls. Every one of those navigation modes depends on decisions designers and developers make long before any assistive technology is involved.
This guide covers how screen readers navigate, why reading order can diverge from visual order, what an accessible name really is, and the specific design and markup choices that quietly break the experience.
How screen readers actually navigate a page
A sighted user scans a page in a fraction of a second, eyes darting to whatever looks relevant. Screen reader users get the same overview through structure, not sight. The three primary navigation tools are headings, landmarks, and links, and each one is a shortcut key away.
Headings are the table of contents
In NVDA and VoiceOver, a single keystroke jumps to the next heading, and users can also jump by level. Many pull up a heading list (NVDA: Insert+F7) and read it like an outline before deciding where to go. This only works if your headings form a logical hierarchy: a single h1 describing the page, h2s for major sections, h3s nested beneath them, with no skipped levels. A common break is choosing a heading tag for its visual size rather than its rank, so a page jumps from h2 to h4 because h4 happened to look right. Style with CSS, choose the level by meaning.
Landmarks frame the page
Landmarks let users skip straight to the main content or jump to navigation. They come from native HTML elements: header maps to the banner role, nav to navigation, main to main, aside to complementary, and footer to contentinfo. Use these elements and most landmarks appear for free. Three rules matter. There should be exactly one main per page. If you have multiple nav or aside regions, give each a distinct aria-label (for example, aria-label="Primary" and aria-label="Breadcrumb") so the user can tell them apart. And do not wrap everything in generic divs, which produces zero landmarks and forces people to read linearly.
Links and forms are their own lists
Users can list every link on a page (NVDA: Insert+F7, or the VoiceOver rotor) and tab through interactive controls. This is where vague link text falls apart: a list of fifteen links all reading "Read more" or "Click here" is useless out of context. Link text should make sense on its own. "Read more about EN 301 549" beats "Read more."
Reading order: when visual order and DOM order disagree
Screen readers announce content in DOM order, the sequence elements appear in the HTML source. Sighted users perceive content in visual order, the sequence pixels appear on screen. Modern CSS makes it trivially easy for these two to diverge, and when they do, the experience becomes incoherent.
The classic offender is reordering with Flexbox or Grid. Setting order: -1 or placing an item in an earlier grid row moves it visually but leaves it untouched in the DOM. A "Step 3" card pulled visually above "Step 1," or a price that appears before the product name on screen but after it in the source, will be read in the wrong sequence. The fix is to make the DOM order match the intended reading order and use CSS only for cosmetic adjustments, not to invent a new sequence.
- Avoid positive tabindex values (tabindex="2" and up). They override natural focus order and almost always create a confusing tab sequence; use 0 or -1 only.
- Watch modals and off-canvas menus. If a dialog is appended to the end of the body but appears centered on screen, focus must be moved into it programmatically or the user lands in the wrong place.
- Test reading order by tabbing through the page with your eyes closed, or by turning the screen reader on and arrowing down line by line. If the narration jumps around, your DOM order is wrong.
Accessible names: what gets announced
Every interactive element has an accessible name, the string a screen reader speaks when focus lands on it. Get this wrong and a button becomes "button," an icon link becomes "link," and a form field becomes a guessing game. The accessible name is computed from several sources in a defined priority order.
- aria-labelledby wins, pointing at the IDs of visible text that names the element.
- aria-label comes next, a string you supply directly. Useful for icon-only buttons, but it overrides visible text, so never let the label contradict what the user sees.
- Native content and associated labels: the text inside a button, the alt on an image, or a label correctly tied to an input via for/id.
- placeholder and title are last-resort fallbacks and should not be relied on; placeholder text vanishes on typing and is often skipped entirely.
Two failures dominate real audits. Icon-only controls (a hamburger menu, a bare magnifying glass, a heart) with no text and no aria-label announce only their role. And images carrying meaning with empty or junk alt text strand the user; an empty alt="" is correct only for decorative images. Forms deserve special care: a visible, programmatically associated label is non-negotiable.
Design decisions that quietly break the experience
Many accessibility failures originate in design and component choices, not in a missing attribute. These are the recurring ones worth catching in design review.
- Divs and spans wired up with click handlers instead of real button or anchor elements. They are not focusable, expose no role, and ignore Enter and Space. Use the native element first.
- CSS that hides content the wrong way. display: none and visibility: hidden remove content from the accessibility tree, which is correct for truly hidden content but wrong for visually hidden text meant to be read (use a clip-based .sr-only utility instead). Conversely, content hidden only with opacity: 0 or off-screen positioning is still announced, so a closed accordion or hidden tab panel can leak its content unless properly hidden.
- Dynamic updates with no announcement. Toast notifications, validation errors, and "added to cart" confirmations that appear silently are invisible to screen reader users. WCAG 2.2 Success Criterion 4.1.3 Status Messages (Level AA) expects these to be conveyed without moving focus; an aria-live region handles it.
- ARIA used to paper over the wrong element. role="button" on a div does not add keyboard behavior, and a wrong or redundant role overrides correct native semantics. The first rule of ARIA is to not use ARIA when HTML already does the job.
- Removing focus styles. outline: none with no visible replacement breaks keyboard navigation for everyone, not just screen reader users, and fails Success Criterion 2.4.7 Focus Visible (Level AA).
None of this is exotic. It is the everyday output of fast component work, which is exactly why screen reader behavior should be checked continuously rather than once a year.
Where this fits into compliance and testing
These practices are not just good craft; in much of the world they are baseline legal requirements. Under the European Accessibility Act, which applies from 28 June 2025, covered digital products and services must meet WCAG 2.2 Level A and AA via the harmonised standard EN 301 549. Headings, accessible names, reading order, and status messages all map directly to specific success criteria in WCAG 2.2.
Automated tooling catches a meaningful slice of these issues, including missing alt text, unlabeled form fields, broken heading order, and empty links. It cannot judge whether your reading order makes sense or whether a link's text is meaningful, which is why manual screen reader passes remain essential. A practical loop is to run an automated check, fix the structural basics, then do a short keyboard-and-screen-reader walkthrough of your key flows. You can start with a free scan at the AccessScan accessibility scanner and work from the accessibility checklist for the manual steps.