Screen Reader Testing

by wshobson46891e7e60daNo licenseListed Oct 8, 2026Updated Oct 8, 2026

Test web applications with screen readers including VoiceOver, NVDA, and JAWS. Use when validating screen reader compatibility, debugging accessibility issues, or ensuring assistive technology support.

Instructions onlySoftware Development
AI-generated overview

Guides testing web applications with screen readers such as VoiceOver, NVDA, JAWS and TalkBack.

What it does
Provides a practical reference for validating web accessibility with screen readers, covering major screen reader and browser combinations, testing priorities, and keyboard commands. It includes checklists and test scripts for navigation, forms, dynamic content, tables and modals, plus ARIA and focus-management code examples for common widgets. It also lists common accessibility issues with fixes and debugging tips.
When to use it
Use when validating screen reader compatibility, testing ARIA implementations, debugging assistive technology issues, or verifying form, navigation and dynamic content accessibility.
Requirements
No scripts or tooling are bundled; it is an instructions-only reference. Actual testing requires the relevant screen readers and platforms, such as VoiceOver on macOS or iOS, NVDA or JAWS on Windows, and TalkBack on Android.

Screen Reader Testing

Practical guide to testing web applications with screen readers for comprehensive accessibility validation.

When to Use This Skill

  • Validating screen reader compatibility
  • Testing ARIA implementations
  • Debugging assistive technology issues
  • Verifying form accessibility
  • Testing dynamic content announcements
  • Ensuring navigation accessibility

Core Concepts

1. Major Screen Readers

Screen ReaderPlatformBrowserUsage
VoiceOvermacOS/iOSSafari~15%
NVDAWindowsFirefox/Chrome~31%
JAWSWindowsChrome/IE~40%
TalkBackAndroidChrome~10%
NarratorWindowsEdge~4%

2. Testing Priority

Minimum Coverage:1. NVDA + Firefox (Windows)2. VoiceOver + Safari (macOS)3. VoiceOver + Safari (iOS)
Comprehensive Coverage:+ JAWS + Chrome (Windows)+ TalkBack + Chrome (Android)+ Narrator + Edge (Windows)

3. Screen Reader Modes

ModePurposeWhen Used
Browse/VirtualRead contentDefault reading
Focus/FormsInteract with controlsFilling forms
ApplicationCustom widgetsARIA applications

VoiceOver (macOS)

Setup

Enable: System Preferences → Accessibility → VoiceOverToggle: Cmd + F5Quick Toggle: Triple-press Touch ID

Essential Commands

Navigation:VO = Ctrl + Option (VoiceOver modifier)
VO + Right Arrow   Next elementVO + Left Arrow    Previous elementVO + Shift + Down  Enter groupVO + Shift + Up    Exit group
Reading:VO + A             Read all from cursorCtrl               Stop speakingVO + B             Read current paragraph
Interaction:VO + Space         Activate elementVO + Shift + M     Open menuTab                Next focusable elementShift + Tab        Previous focusable element
Rotor (VO + U):Navigate by: Headings, Links, Forms, LandmarksLeft/Right Arrow   Change rotor categoryUp/Down Arrow      Navigate within categoryEnter              Go to item
Web Specific:VO + Cmd + H       Next headingVO + Cmd + J       Next form controlVO + Cmd + L       Next linkVO + Cmd + T       Next table

Testing Checklist

markdown
## VoiceOver Testing Checklist
### Page Load
- [ ] Page title announced- [ ] Main landmark found- [ ] Skip link works
### Navigation
- [ ] All headings discoverable via rotor- [ ] Heading levels logical (H1 → H2 → H3)- [ ] Landmarks properly labeled- [ ] Skip links functional
### Links & Buttons
- [ ] Link purpose clear- [ ] Button actions described- [ ] New window/tab announced
### Forms
- [ ] All labels read with inputs- [ ] Required fields announced- [ ] Error messages read- [ ] Instructions available- [ ] Focus moves to errors
### Dynamic Content
- [ ] Alerts announced immediately- [ ] Loading states communicated- [ ] Content updates announced- [ ] Modals trap focus correctly
### Tables
- [ ] Headers associated with cells- [ ] Table navigation works- [ ] Complex tables have captions

Common Issues & Fixes

html
<!-- Issue: Button not announcing purpose --><button><svg>...</svg></button>
<!-- Fix --><button aria-label="Close dialog"><svg aria-hidden="true">...</svg></button>
<!-- Issue: Dynamic content not announced --><div id="results">New results loaded</div>
<!-- Fix --><div id="results" role="status" aria-live="polite">New results loaded</div>
<!-- Issue: Form error not read --><input type="email" /><span class="error">Invalid email</span>
<!-- Fix --><input type="email" aria-invalid="true" aria-describedby="email-error" /><span id="email-error" role="alert">Invalid email</span>

NVDA (Windows)

Setup

Download: nvaccess.orgStart: Ctrl + Alt + NStop: Insert + Q

Essential Commands

Navigation:Insert = NVDA modifier
Down Arrow         Next lineUp Arrow           Previous lineTab                Next focusableShift + Tab        Previous focusable
Reading:NVDA + Down Arrow  Say allCtrl               Stop speechNVDA + Up Arrow    Current line
Headings:H                  Next headingShift + H          Previous heading1-6                Heading level 1-6
Forms:F                  Next form fieldB                  Next buttonE                  Next edit fieldX                  Next checkboxC                  Next combo box
Links:K                  Next linkU                  Next unvisited linkV                  Next visited link
Landmarks:D                  Next landmarkShift + D          Previous landmark
Tables:T                  Next tableCtrl + Alt + Arrows Navigate cells
Elements List (NVDA + F7):Shows all links, headings, form fields, landmarks

Browse vs Focus Mode

NVDA automatically switches modes:- Browse Mode: Arrow keys navigate content- Focus Mode: Arrow keys control interactive elements
Manual switch: NVDA + Space
Watch for:- "Browse mode" announcement when navigating- "Focus mode" when entering form fields- Application role forces forms mode

Testing Script

markdown
## NVDA Test Script
### Initial Load
1. Navigate to page2. Let page finish loading3. Press Insert + Down to read all4. Note: Page title, main content identified?
### Landmark Navigation
1. Press D repeatedly2. Check: All main areas reachable?3. Check: Landmarks properly labeled?
### Heading Navigation
1. Press Insert + F7 → Headings2. Check: Logical heading structure?3. Press H to navigate headings4. Check: All sections discoverable?
### Form Testing
1. Press F to find first form field2. Check: Label read?3. Fill in invalid data4. Submit form5. Check: Errors announced?6. Check: Focus moved to error?
### Interactive Elements
1. Tab through all interactive elements2. Check: Each announces role and state3. Activate buttons with Enter/Space4. Check: Result announced?
### Dynamic Content
1. Trigger content update2. Check: Change announced?3. Open modal4. Check: Focus trapped?5. Close modal6. Check: Focus returns?

JAWS (Windows)

Essential Commands

Start: Desktop shortcut or Ctrl + Alt + JVirtual Cursor: Auto-enabled in browsers
Navigation:Arrow keys         Navigate contentTab                Next focusableInsert + Down      Read allCtrl               Stop speech
Quick Keys:H                  Next headingT                  Next tableF                  Next form fieldB                  Next buttonG                  Next graphicL                  Next list;                  Next landmark
Forms Mode:Enter              Enter forms modeNumpad +           Exit forms modeF5                 List form fields
Lists:Insert + F7        Link listInsert + F6        Heading listInsert + F5        Form field list
Tables:Ctrl + Alt + Arrows Table navigation

TalkBack (Android)

Setup

Enable: Settings → Accessibility → TalkBackToggle: Hold both volume buttons 3 seconds

Gestures

Explore: Drag finger across screenNext: Swipe rightPrevious: Swipe leftActivate: Double tapScroll: Two finger swipe
Reading Controls (swipe up then right):- Headings- Links- Controls- Characters- Words- Lines- Paragraphs

Common Test Scenarios

1. Modal Dialog

html
<!-- Accessible modal structure --><div  role="dialog"  aria-modal="true"  aria-labelledby="dialog-title"  aria-describedby="dialog-desc">  <h2 id="dialog-title">Confirm Delete</h2>  <p id="dialog-desc">This action cannot be undone.</p>  <button>Cancel</button>  <button>Delete</button></div>
javascript
// Focus managementfunction openModal(modal) {  // Store last focused element  lastFocus = document.activeElement;
  // Move focus to modal  modal.querySelector("h2").focus();
  // Trap focus  modal.addEventListener("keydown", trapFocus);}
function closeModal(modal) {  // Return focus  lastFocus.focus();}
function trapFocus(e) {  if (e.key === "Tab") {    const focusable = modal.querySelectorAll(      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',    );    const first = focusable[0];    const last = focusable[focusable.length - 1];
    if (e.shiftKey && document.activeElement === first) {      last.focus();      e.preventDefault();    } else if (!e.shiftKey && document.activeElement === last) {      first.focus();      e.preventDefault();    }  }
  if (e.key === "Escape") {    closeModal(modal);  }}

2. Live Regions

html
<!-- Status messages (polite) --><div role="status" aria-live="polite" aria-atomic="true">  <!-- Content updates will be announced after current speech --></div>
<!-- Alerts (assertive) --><div role="alert" aria-live="assertive">  <!-- Content updates interrupt current speech --></div>
<!-- Progress updates --><div  role="progressbar"  aria-valuenow="75"  aria-valuemin="0"  aria-valuemax="100"  aria-label="Upload progress"></div>
<!-- Log (additions only) --><div role="log" aria-live="polite" aria-relevant="additions">  <!-- New messages announced, removals not --></div>

3. Tab Interface

html
<div role="tablist" aria-label="Product information">  <button role="tab" id="tab-1" aria-selected="true" aria-controls="panel-1">    Description  </button>  <button    role="tab"    id="tab-2"    aria-selected="false"    aria-controls="panel-2"    tabindex="-1"  >    Reviews  </button></div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">  Product description content...</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>  Reviews content...</div>
javascript
// Tab keyboard navigationtablist.addEventListener("keydown", (e) => {  const tabs = [...tablist.querySelectorAll('[role="tab"]')];  const index = tabs.indexOf(document.activeElement);
  let newIndex;  switch (e.key) {    case "ArrowRight":      newIndex = (index + 1) % tabs.length;      break;    case "ArrowLeft":      newIndex = (index - 1 + tabs.length) % tabs.length;      break;    case "Home":      newIndex = 0;      break;    case "End":      newIndex = tabs.length - 1;      break;    default:      return;  }
  tabs[newIndex].focus();  activateTab(tabs[newIndex]);  e.preventDefault();});

Debugging Tips

javascript
// Log what screen reader seesfunction logAccessibleName(element) {  const computed = window.getComputedStyle(element);  console.log({    role: element.getAttribute("role") || element.tagName,    name:      element.getAttribute("aria-label") ||      element.getAttribute("aria-labelledby") ||      element.textContent,    state: {      expanded: element.getAttribute("aria-expanded"),      selected: element.getAttribute("aria-selected"),      checked: element.getAttribute("aria-checked"),      disabled: element.disabled,    },    visible: computed.display !== "none" && computed.visibility !== "hidden",  });}

Best Practices

Do's

  • Test with actual screen readers - Not just simulators
  • Use semantic HTML first - ARIA is supplemental
  • Test in browse and focus modes - Different experiences
  • Verify focus management - Especially for SPAs
  • Test keyboard only first - Foundation for SR testing

Don'ts

  • Don't assume one SR is enough - Test multiple
  • Don't ignore mobile - Growing user base
  • Don't test only happy path - Test error states
  • Don't skip dynamic content - Most common issues
  • Don't rely on visual testing - Different experience

Source and attribution

Source:wshobson/agentsinplugins/accessibility-compliance/skills/screen-reader-testingat commit46891e7

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal