Home/Learn/HTML, CSS & Responsive/Accessible Structure — What a Screen Reader Hears

Accessible Structure — What a Screen Reader Hears

Intermediate
Accessibility

A screen reader builds its own picture of your page from the accessibility tree. Understanding what that tree contains explains why semantic markup is not a formality.

Overview

Accessibility becomes concrete once you know what a screen reader actually does. It does not read the page top to bottom — users navigate by headings, landmarks, links and form fields, jumping between them. That navigation is built from the accessibility tree, which the browser derives from your HTML: each element contributes a role, a name, a state and a value. A div contributes none of those, which is why a page of divs offers nothing to navigate by. Every accessibility technique after this one is about filling that tree correctly.

The Accessibility Tree

Role, name, state, value — the four things assistive technology reads.

Role, name, state — inspect it in DevTools
<button aria-pressed="true">Bookmark</button>
// role: button   name: "Bookmark"   state: pressed

<input type="checkbox" id="pro" checked>
<label for="pro">Pro only</label>
// role: checkbox   name: "Pro only"   state: checked

<div onclick="…">Bookmark</div>
// role: generic   name: (none)   state: (none)
// Announced as nothing. Not reachable. Not operable.

/* Inspect it: DevTools -> Elements -> Accessibility panel shows the
   computed role and name for the selected element. If the name is
   empty or the role is "generic" on something interactive, that is
   your bug, visible before any screen reader is involved. */

/* The accessible NAME is computed in a fixed priority order:
     aria-labelledby > aria-label > the native label / content >
     title attribute
   So an aria-label silently overrides visible text — which is why a
   button reading "Save" can announce something completely different. */

<button aria-label="Close">Save</button>    // announces "Close". Bug.

How Users Navigate

The shortcuts that make semantic markup pay off.

Headings, landmarks, and a skip link
/* Screen-reader users rarely read linearly. They:
     - press H to jump between headings
     - press D or use a landmarks menu to jump between regions
     - open a list of every LINK on the page
     - open a list of every FORM FIELD
     - press T to move between tables

   Every one of those lists is built from your elements. A page of
   divs offers empty lists. */

/* Which is why these three matter more than any ARIA attribute:
     1. a correct heading outline
     2. landmarks (header, nav, main, aside, footer)
     3. link and button text that makes sense out of context      */

/* A skip link — the first focusable element on the page, so a
   keyboard user can bypass the navigation on every single page */
<a href="#main" class="skip-link">Skip to content</a>
<main id="main" tabindex="-1">

.skip-link {
  position: absolute; inset-block-start: -3rem;
  background: var(--surface); padding: .5rem 1rem;
}
.skip-link:focus { inset-block-start: 0 }    /* visible only on focus */

/* tabindex="-1" on the target makes focus actually move there, not
   just the scroll position. */

Hiding Things Correctly

Four ways to hide something, each with different consequences.

sr-only, aria-hidden, inert
/* Hidden from everyone */
display: none
visibility: hidden
<div hidden>

/* Hidden visually, still announced — for screen-reader-only text */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}
<button><svg aria-hidden="true"/><span class="sr-only">Delete</span></button>

/* Visible, but hidden from screen readers — for decorative content
   whose meaning is already conveyed in text */
<svg aria-hidden="true">
<span aria-hidden="true">→</span>

/* NEVER put aria-hidden on something focusable: it creates a
   control a screen-reader user can tab to but cannot perceive. */
<button aria-hidden="true">Save</button>      // a trap

/* Hiding a whole inert region behind a modal */
<div inert>…</div>      // unfocusable AND unreachable, in one attribute

/* The common mistake: hiding a mobile menu with display: none but
   leaving its links tabbable in a version hidden with opacity or
   visibility — the user tabs into invisible links. */

Key Points to Remember

  • 1Assistive technology reads a role, name, state and value for each element — a div supplies none of them
  • 2The accessible name has a priority order, so aria-label silently overrides visible text
  • 3Users navigate by headings, landmarks, links and form fields rather than reading linearly
  • 4A skip link as the first focusable element lets keyboard users bypass navigation on every page
  • 5.sr-only hides visually but keeps content announced; aria-hidden on a focusable element creates an unusable control

Interview Questions

Sign in to ask Aria
1

What is the accessibility tree and how does it relate to your HTML?

Medium
2

How do screen-reader users typically navigate a page?

Medium
3

What is the difference between display: none, aria-hidden and a .sr-only class?

Hard

Ask Aria about Accessible Structure — What a Screen Reader Hears

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…