Understand how screen readers interpret your UI, when to use ARIA roles and properties, and how design decisions translate to screen reader announcements.
Understand how screen readers interpret your UI, when to use ARIA roles and properties, and how design decisions translate to screen reader announcements.
Screen readers (NVDA, JAWS, VoiceOver, TalkBack) convert visual content to speech or Braille by reading the accessibility tree — a simplified model of the DOM. Understanding what gets announced helps designers make better decisions.
For a button: screen reader announces 'Button label, button' (name then role). If the button is disabled: 'Submit, dimmed, button'. If the button is in a loading state: 'Submit, busy'. For an image: announces the alt text if present, or 'image' with filename if alt is missing, or nothing if alt is empty.
For form inputs, a well-implemented input announces: 'First name, required, edit field' (label, required flag, role). For a checkbox: 'Subscribe to newsletter, unchecked, checkbox'. For a custom dropdown: the ARIA role determines what the screen reader says — a div with role='listbox' announces differently than a native select.
The most important design implication: every piece of information conveyed visually must be conveyed to screen readers via text (alt, aria-label, aria-describedby) or semantic HTML. Color, position, and visual grouping alone are not perceived by screen readers.
Screen reader users navigate pages by landmarks — ARIA role designations that let them jump between page sections without reading every element. Map your page layout to landmark roles:
1<!-- Landmark structure for a typical page -->
2<body>
3 <!-- role="banner" = site-wide header -->
4 <header role="banner">
5 <a href="/">Brand Logo</a>
6 <nav role="navigation" aria-label="Main navigation">
7 <!-- Primary nav links -->
8 </nav>
9 </header>
10
11 <!-- role="main" = primary content -->
12 <main role="main" id="main-content">
13 <!-- All primary page content -->
14
15 <!-- role="search" = search widget -->
16 <search role="search" aria-label="Product search">
17 <input type="search" />
18 </search>
19
20 <!-- role="region" = named content region -->
21 <section role="region" aria-labelledby="featured-heading">
22 <h2 id="featured-heading">Featured Courses</h2>
23 </section>
24 </main>
25
26 <!-- role="complementary" = sidebar content -->
27 <aside role="complementary" aria-label="Quick links">
28 </aside>
29
30 <!-- role="contentinfo" = site-wide footer -->
31 <footer role="contentinfo">
32 </footer>
33</body>Native HTML5 elements (header, nav, main, aside, footer) carry implicit landmark roles — use them preferentially over div with role attribute.
Every interactive element needs an accessible name. Three methods, in priority order (higher = preferred):
aria-labelledby (highest priority): references the ID of an existing visible element as the label. Preferred because it reuses visible text that sighted users see.
1<h2 id="account-section">Account Settings</h2>
2<button aria-labelledby="account-section delete-btn-label">
3 <span id="delete-btn-label">Delete</span>
4</button>
5<!-- Screen reader: "Account Settings Delete, button" -->aria-label: provides an accessible name that is not visible in the UI. Use when there is no visible text to reference.
1<!-- Icon-only button -->
2<button aria-label="Close dialog">
3 <svg aria-hidden="true"><!-- X icon --></svg>
4</button>
5
6<!-- Multiple search forms on one page -->
7<input type="search" aria-label="Search products" />
8<input type="search" aria-label="Search help articles" />aria-describedby: provides supplementary description, announced after the accessible name. Use for hints, error messages, and character counts:
1<label for="password">Password</label>
2<input
3 id="password"
4 type="password"
5 aria-describedby="password-hint password-error"
6 aria-invalid="true"
7/>
8<p id="password-hint">At least 8 characters with one number.</p>
9<p id="password-error" role="alert">Password too short.</p>
10<!-- Announces: "Password, invalid data, edit field, At least 8 characters with one number. Password too short." -->When content updates without focus movement (search results loading, cart total updating, status notifications), aria-live regions cause screen readers to announce the update automatically:
1// Toast notification accessible to screen readers
2function ToastContainer() {
3 const { toasts } = useToasts();
4
5 return (
6 <>
7 {/* polite: announces after current speech finishes */}
8 <div
9 role="status"
10 aria-live="polite"
11 aria-atomic="true"
12 aria-relevant="additions"
13 className="sr-only" // visually hidden but announced
14 >
15 {toasts.filter(t => t.type !== 'error').map(t => (
16 <p key={t.id}>{t.message}</p>
17 ))}
18 </div>
19
20 {/* assertive: interrupts current speech immediately */}
21 {/* Use ONLY for errors and critical alerts */}
22 <div
23 role="alert"
24 aria-live="assertive"
25 aria-atomic="true"
26 className="sr-only"
27 >
28 {toasts.filter(t => t.type === 'error').map(t => (
29 <p key={t.id}>{t.message}</p>
30 ))}
31 </div>
32
33 {/* Visual toasts (separate from the live region) */}
34 <div className="toast-visual-container">
35 {toasts.map(t => <Toast key={t.id} toast={t} />)}
36 </div>
37 </>
38 );
39}Key rules: use aria-live="polite" for most updates; only use aria-live="assertive" (or role="alert") for errors and critical time-sensitive information. Assertive live regions interrupt the user mid-sentence.
Screen reader testing during development catches announcement issues that automated tools miss. Two most important testing combinations: NVDA + Chrome on Windows (most common for desktop screen reader users), and VoiceOver + Safari on macOS/iOS (standard for Apple devices).
NVDA quickstart for developers:
1Install NVDA (free): nvaccess.org/download
2
3Key shortcuts:
4Caps Lock = NVDA modifier key (also Insert key)
5NVDA+N = open menu
6NVDA+Space = toggle browse mode / focus mode
7Tab = navigate focusable elements
8H = jump to next heading
9F = jump to next form field
10B = jump to next button
111-6 = jump to heading level 1-6
12NVDA+F7 = list all headings, links, landmarksTest workflow for a new component: open in Chrome with NVDA running, press Tab to reach the component, listen to the full announcement (name, role, state, description), interact with it using keyboard, listen to state change announcements. Compare what you hear to what sighted users see. Fix any gaps where visual information is not conveyed in the audio announcement.
Install NVDA and navigate your most complex form using only keyboard and listening to the announcements. Check that every label, error message, and required indicator is announced correctly. Document what you hear vs. what you expected.
Your progress is saved in this browser, on this device only.