Use when designing or auditing for screen reader compatibility — semantic HTML, ARIA roles and states, live regions, announcements, reading order, and hidden content. Trigger when the user mentions screen reader, VoiceOver, NVDA, JAWS, accessibility tree, ARIA, or assistive technology.
Screen readers traverse the accessibility tree, not the visual layout. If the tree is wrong, the interface is wrong for blind and low-vision users.
Screen readers understand HTML elements natively. ARIA is a patch for when HTML can't express the role.
| Need | Use HTML | Not |
|---|---|---|
| Button | <button> | <div role="button"> |
| Link | <a href> | <span onClick> |
| Heading | <h1>–<h6> | <div class="heading"> |
| List | <ul>/<ol> | <div class="list"> |
| Table | <table> with <th> | CSS grid of <div> |
| Landmark | <main>, <nav>, , |
<header><footer><div class="nav"> |
| Dialog | <dialog> | <div class="modal"> |
Headings create the document outline. Screen reader users navigate by heading. Skip levels or misused headings break this navigation.
<h1> per page.<h3> because you want smaller text; use CSS.The first rule of ARIA: don't use ARIA if HTML does the job. The second rule: if you use ARIA, use it correctly and completely.
Common ARIA patterns done right:
<!-- Live region for dynamic updates -->
<div role="status" aria-live="polite">3 results found</div>
<!-- Custom disclosure -->
<button aria-expanded="false" aria-controls="panel">Details</button>
<div id="panel" hidden>...</div>
<!-- Tab interface → see canon-tabs -->
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
Use for: labels that are visually implied, skip links before focus, extra context.
| Role | Urgency | Use |
|---|---|---|
role="status" / aria-live="polite" | Low — waits for pause | Search result count, save confirmation |
role="alert" / aria-live="assertive" | High — interrupts | Error messages, time-sensitive warnings |
The container must exist in the DOM before content is injected. Don't mount and unmount live-region elements.
| Anti-pattern | Why it fails |
|---|---|
aria-label on a non-interactive element | Most screen readers ignore it on <div> |
Redundant ARIA (<button role="button">) | Noise |
aria-hidden="true" on focusable elements | Focus lands on invisible element |
| Using only color/icon for status | Screen reader doesn't announce visual changes |
| Heading levels skipped | Navigation breaks |
display: none for "hiding" content from sighted users but wanting SR to read it | Hides from everyone |
Test with at least one screen reader:
Quick smoke test: navigate the entire page with Tab + arrow keys while listening.
<h1>, no skipped heading levels<main>, <nav>, <header>, <footer> presentalt or alt="".sr-only class available for hidden labelsaria-hidden on focusable elements