Generated banner for Screen Reader Testing
Generated logo for Screen Reader Testing

Screen Reader Testing

Seth HobsonCommunityCreated by Seth Hobson

  • Skill
  • Guidelines
  • Software development
  • MIT
  • No issues found

From the author

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.
— the description by Seth Hobson

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a structured approach to validating web accessibility by testing directly with assistive technology. It covers the essential screen readers used by the majority of users, outlines testing priorities, and provides practical patterns for common UI components like modals, live regions, and tab interfaces. The guide emphasizes the importance of testing with real hardware and software rather than relying solely on simulators or automated tools, ensuring that dynamic content, focus management, and semantic structures are accessible to all users.

What it does

  • Defines a testing hierarchy for major screen readers including NVDA, JAWS, and VoiceOver.
  • Provides keyboard command references for navigating content, forms, and landmarks.
  • Offers code patterns for accessible modals, live regions, and tab interfaces.
  • Includes a debugging utility to log the accessible name, role, and state of DOM elements.
  • Explains the distinction between browse and focus modes in screen reader software.
  • Supplies a structured test script for systematic accessibility verification.

When to use it

  • When validating the accessibility of a new web feature or component.
  • When debugging issues where assistive technology fails to announce content correctly.
  • When ensuring that dynamic updates in a single-page application are properly communicated.
  • When performing a comprehensive accessibility audit of a web application.
  • When verifying that keyboard navigation and focus management are implemented correctly.

Advantages

  • Promotes testing with actual screen readers for authentic user experience validation.
  • Reduces accessibility bugs by providing clear implementation patterns for common UI elements.
  • Standardizes the testing process across different platforms and browsers.
  • Helps developers understand how screen readers interpret semantic HTML and ARIA attributes.
  • Provides actionable debugging techniques to inspect the accessibility tree.

Usage examples

“How do I test if my modal dialog is accessible to screen reader users?”
The skill provides a specific modal structure using role='dialog' and aria-modal='true', along with JavaScript examples for trapping focus and managing focus return, which you can use to verify your implementation.
“What are the essential NVDA commands for navigating a web page?”
The skill lists key NVDA commands including 'H' for headings, 'F' for form fields, 'K' for links, and 'Insert + Down' to read all content, along with instructions for switching between browse and focus modes.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/wshobson/screen-reader-testing/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip screen-reader-testing.zip -d .agents/skills

Documentation by Seth Hobson

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

Before testing on macOS, read VoiceOver setup, commands, and checklists.

For the required iOS Safari coverage, read VoiceOver touch setup and gestures.

Before testing with NVDA, read NVDA setup, modes, commands, and test scripts.

Before testing with JAWS or TalkBack, read platform setup and commands.

Common Test Scenarios

1. Modal Dialog
<!-- 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>
// Focus management
function 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
<!-- 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
<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>
// Tab keyboard navigation
tablist.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

// Log what screen reader sees
function 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

Files

references/voiceover.md

VoiceOver (macOS)

Setup
Enable: System Settings → Accessibility → VoiceOver
Toggle: Cmd + F5
Quick Toggle: Hold Command while quickly pressing Touch ID three times

Source: Apple VoiceOver setup.

Essential Commands
Navigation:
VO = Ctrl + Option (VoiceOver modifier)

VO + Right Arrow   Next element
VO + Left Arrow    Previous element
VO + Shift + Down  Enter group
VO + Shift + Up    Exit group

Reading:
VO + A             Read all from cursor
Ctrl               Stop speaking
VO + P             Read current paragraph

Interaction:
VO + Space         Activate element
VO + Shift + M     Open menu
Tab                Next focusable element
Shift + Tab        Previous focusable element

Rotor (VO + U):
Navigate by: Headings, Links, Forms, Landmarks
Left/Right Arrow   Change rotor category
Up/Down Arrow      Navigate within category
Enter              Go to item

Web Specific:
VO + Cmd + H       Next heading
VO + Cmd + J       Next form control
VO + Cmd + L       Next link
VO + Cmd + T       Next table
Testing Checklist
## 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
<!-- 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>

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Seth Hobson
Publisher
Seth Hobson (Community)
Source
wshobson/agents · plugins/accessibility-compliance/skills/screen-reader-testing · commit 4bde316
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Seth Hobson.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download screen-reader-testing.zip