Manual checks: the screen-reader audit
Listening to your own UI is uncomfortable, embarrassing, and one of the most useful things you can do.
The first time you turn a screen reader on, your own app will sound terrible. Buttons announced as "group, group, group." Modals that don't say they're modals. Form errors that disappear into the void. This is normal. It's also the point — these are bugs your users have been hitting for months, and you have just found them.
This lesson is not "learn to use a screen reader as a daily driver" — that takes years. It's "learn enough to find and reproduce real bugs in 30–45 minutes per page."
Picking a screen reader
You don't need to learn five. Pair the one closest to hand for your platform with at least one other for cross-checking:
| Screen reader | Platform | Cost | Notes | |---|---|---|---| | VoiceOver | macOS, iOS | Built in | Default on Apple devices. Test here even if your team is on Windows. | | NVDA | Windows | Free | The most-used screen reader globally, alongside JAWS. | | JAWS | Windows | Paid (free in 40-min demo mode) | Many enterprise users. Behaviour sometimes differs from NVDA. | | TalkBack | Android | Built in | For mobile audits. |
For desktop testing on a Mac team, the practical pair is VoiceOver in Safari and NVDA in Firefox or Chrome on Windows (or a Windows VM). Pairings matter — screen readers and browsers are tested against each other in specific combinations, so VoiceOver-with-Chrome is a known-rougher experience.
Close your eyes for some of the audit. It feels theatrical and it works. You stop reading the screen and start listening to what the screen reader is actually saying.
VoiceOver basics
Turn it on and off with Cmd + F5. Once on, the VO modifier is Control + Option (you can lock it with Caps Lock to save your fingers).
| Action | Keys |
|---|---|
| Read next item | VO + → |
| Read previous item | VO + ← |
| Jump to next heading / link / form field | VO + Cmd + H / L / J |
| Open the rotor (a list of headings, links, etc.) | VO + U |
| Interact with a group | VO + Shift + ↓ (and ↑ to step out) |
| Activate the focused control | VO + Space |
The rotor is the move that pays off fastest. Hit VO + U, arrow through the categories — Headings, Links, Form Controls, Landmarks — and you can see your page's structure as a screen-reader user would.
If your "headings" list is empty or makes no sense as an outline, that's already a finding — heading structure is one of the main ways screen-reader users skim a page.
NVDA basics
NVDA on Windows uses Insert (or Caps Lock if you toggle the option) as its modifier. Browse mode (the default for web) lets you read the page like a document; focus mode is for forms and interactive widgets, and NVDA switches automatically.
| Action | Keys |
|---|---|
| Read next item | ↓ |
| Next heading | H (or 1–6 for level) |
| Next link | K |
| Next form field | F |
| Next landmark | D |
| Open the elements list | Insert + F7 |
| Activate | Enter or Space |
The elements list (Insert + F7) is NVDA's equivalent of the rotor. Same purpose: see the page's structure at a glance.
A fair learning curve note: expect to spend 20–30 minutes getting comfortable with each tool the first time. After that, audits go quickly.
What to listen for
A short, ruthless checklist:
- Does every interactive element announce three things? Its role ("button," "link," "checkbox"), its name ("Subscribe to newsletter"), and its state ("pressed," "checked," "expanded"). Missing any of those is a finding.
- Are images announced sensibly? Decorative images should be silent (empty
alt=""); meaningful images should describe their meaning, not their filename. - Do form fields read with their label? Tab into a field. You should hear the label, then the type, then any help or error text. "Edit text, blank" is a missing label.
- Do dynamic changes announce themselves? Adding a "saved" toast that no one hears is a non-event. Use a live region (we covered these in the ARIA course).
- Do modals announce as dialogs? Opening one should say "dialog" or similar, not "group" or nothing at all.
- Is there a logical reading order? Listen to the page top-to-bottom with arrow keys. Things should arrive in an order that makes sense.
Patterns by component
The same handful of components break in the same handful of ways. A short field guide:
- Custom buttons. A
<div onclick>with norole="button"is announced as a group at best. Use a real<button>. If you can't, addrole="button",tabindex="0", and a key handler forEnterandSpace. - Toggles. A button that flips state needs
aria-pressed="true|false"so the screen reader announces "pressed" / "not pressed." - Disclosure widgets / accordions. The trigger needs
aria-expanded="true|false"andaria-controlspointing at the panel. - Tabs. The pattern is
role="tablist"containingrole="tab"items witharia-selected, controllingrole="tabpanel"regions. Get this one wrong and the whole component sounds like soup. - Forms. Every input needs an associated
<label>(either wrapping or viafor=/id=). Errors should be referenced byaria-describedbyso they're read when the field is focused. - Dialogs.
role="dialog"(orrole="alertdialog"for ones the user must dismiss),aria-modal="true", and anaria-labelledbypointing at the dialog's heading. - Live updates. Use
aria-live="polite"for non-urgent announcements (a saved toast),aria-live="assertive"only for things that genuinely interrupt (a stop-the-form error).
Test with the screen reader you have, but verify the bug in a second one before you put a high priority on it. Some oddities are reader-specific quirks, not real bugs.
Check yourself
VO + U on a long article page and the rotor's "Headings" list is empty. What does that suggest?