npx skills add https://github.com/wshobson/agents --skill screen-reader-testingHow Screen Reader Testing fits into a Paperclip company.
Screen Reader Testing drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.
Pre-configured AI company — 18 agents, 18 skills, one-time purchase.
SKILL.md538 linesExpandCollapse
---name: screen-reader-testingdescription: 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.--- # 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 Reader | Platform | Browser | Usage || ------------- | --------- | -------------- | ----- || **VoiceOver** | macOS/iOS | Safari | ~15% || **NVDA** | Windows | Firefox/Chrome | ~31% || **JAWS** | Windows | Chrome/IE | ~40% || **TalkBack** | Android | Chrome | ~10% || **Narrator** | Windows | Edge | ~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 | Mode | Purpose | When Used || ------------------ | ---------------------- | ----------------- || **Browse/Virtual** | Read content | Default reading || **Focus/Forms** | Interact with controls | Filling forms || **Application** | Custom widgets | ARIA 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 experienceAccessibility Compliance
This walks you through implementing proper WCAG 2.2 compliance with real code patterns for screen readers, keyboard navigation, and mobile accessibility. It cov
Airflow Dag Patterns
If you're building data pipelines with Airflow, this skill gives you production-ready DAG patterns that actually work in the real world. It covers TaskFlow API
Angular Migration
Migrating from AngularJS to Angular is notoriously painful, and this skill tackles the practical stuff that makes or breaks these projects. It covers hybrid app