Screen Readers and Assistive Technology
ARIA attributes, semantic HTML, and screen reader testing fundamentals.
Question: What are ARIA roles and when should you use them?β
Difficulty: π‘ Medium | Frequency: ββββ | Companies: Google, Meta, Airbnb
The First Rule of ARIAβ
Don't use ARIA. Use semantic HTML first. When you use a native HTML element, the browser handles role, state, and interaction automatically.
<!-- β Bad: div lacks built-in keyboard support and semantics -->
<div onclick="toggleMenu()">Menu</div>
<!-- β
Better: button has role="button", keyboard support, focus handling -->
<button onclick="toggleMenu()">Menu</button>
<!-- β
When truly needed: custom widget with full ARIA -->
<div
role="button"
tabindex="0"
aria-label="Toggle menu"
aria-expanded="false"
onclick="toggleMenu()"
onkeypress="if(event.key==='Enter'||event.key===' ') toggleMenu()"
>
Menu
</div>
Popular Screen Readersβ
| Screen Reader | Platform | Cost |
|---|---|---|
| NVDA | Windows | Free |
| JAWS | Windows | Paid |
| VoiceOver | macOS / iOS | Built-in |
| TalkBack | Android | Built-in |
| Narrator | Windows | Built-in |
How Screen Readers Work: The Accessibility Treeβ
The browser maintains two parallel structures:
- DOM Tree β the actual HTML
- Accessibility Tree β a simplified version consumed by screen readers
ARIA modifies the accessibility tree without changing the DOM. When you add role="button" to a div, the accessibility tree shows a button β but you still must implement keyboard behavior manually.
<!-- DOM: div element -->
<div role="button" tabindex="0" aria-label="Close">Γ</div>
<!-- Accessibility tree node:
Role: button
Name: "Close"
State: focusable
(keyboard behavior: you must implement)
-->
Common ARIA Patternsβ
Navigation with Landmarkβ
<nav aria-label="Main navigation">
<ul>
<li><a href="/" aria-current="page">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
Live Region for Dynamic Contentβ
<!-- Alerts: announced immediately -->
<div role="alert">Payment failed: card declined</div>
<!-- Status: announced when user is idle -->
<div role="status" aria-live="polite">Changes saved</div>
Accessible Imageβ
<!-- Informative: describe the meaning -->
<img src="chart.png" alt="Sales grew 45% from Q1 to Q2 2024">
<!-- Decorative: empty alt -->
<img src="divider.png" alt="">
Accessible Custom Widgetβ
<div role="tablist" aria-label="Product tabs">
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">
Description
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2" tabindex="-1">
Reviews
</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">...</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>...</div>
Testing with Screen Readersβ
NVDA (Windows β Free)β
- Download from nvaccess.org
- Start NVDA β your screen reader is active
- Key shortcuts:
Hβ next headingDβ next landmarkTabβ next focusable elementEnterβ activate link/buttonNVDA+Spaceβ switch between Browse/Forms mode
VoiceOver (macOS β Built-in)β
- Enable:
Cmd+F5or System Preferences β Accessibility β VoiceOver - Key shortcuts:
VO+Uβ open Rotor (headings, links, landmarks)VO+Right/Leftβ navigate elementsVO+Spaceβ activate elementCmd+F5β toggle VoiceOver
What to Testβ
- Page title announced on load
- All images have appropriate alt text
- Form labels are announced with inputs
- Error messages are announced (via
role="alert"oraria-describedby) - Modal dialogs trap focus and announce title
- Dynamic updates are announced (live regions)
- Navigation landmarks are distinguishable
- Custom widgets respond to keyboard
Screen Reader Announcement Orderβ
Different screen readers announce elements in slightly different order:
NVDA example for a button:
"Submit, button" (name β role)
With description:
"Submit, button, This will create your account" (name β role β description)
When an error appears with role="alert":
"Error: Please enter a valid email address" (interrupts immediately)
Common Accessibility Mistakes Screen Readers Catchβ
<!-- β Image with no alt -->
<img src="product.jpg">
<!-- SR: "product.jpg, image" (useless) -->
<!-- β Button with no name -->
<button>Γ</button>
<!-- SR: "Γ, button" (unclear) -->
<!-- β Form field with no label -->
<input type="email" placeholder="Email">
<!-- SR: "email, edit text" (from placeholder β disappears when typing) -->
<!-- β Click handler on non-interactive element -->
<div onclick="handleClick()">Open menu</div>
<!-- SR: "Open menu" β no role, not keyboard accessible -->
Content from Frontend-Master-Prep-Series β 08-accessibility