🔊 Screen Reader Compatibility Guide
Ensuring Claude Code and AI-generated code work seamlessly with screen readers for developers who are blind or have low vision.
🎯 Overview
Screen readers are essential assistive technologies that convert digital text and interface elements into speech or braille output. This guide covers how to ensure Claude Code interactions and generated code are fully compatible with popular screen readers.
📊 Popular Screen Readers
Desktop Screen Readers
- NVDA (Windows) - Free, open-source, 30.8% market share
- JAWS (Windows) - Commercial, most features, 33.5% market share
- VoiceOver (macOS/iOS) - Built-in Apple screen reader, 20.4% market share
- Orca (Linux) - Free, open-source for GNOME desktop
Mobile Screen Readers
- VoiceOver (iOS) - 71% of mobile screen reader users
- TalkBack (Android) - 29% of mobile screen reader users
🛠️ Implementation Best Practices
1. Semantic HTML First
<!-- ❌ Bad: Non-semantic markup -->
<div onclick="handleClick()">Submit</div>
<!-- ✅ Good: Semantic HTML -->
<button type="submit">Submit</button>Why it matters: Screen readers rely on semantic HTML to understand element purposes and convey them to users.
2. ARIA Labels and Descriptions
<!-- For elements without visible labels -->
<input type="search" aria-label="Search documentation">
<!-- For complex interactions -->
<button aria-label="Delete item" aria-describedby="delete-warning">
<svg><!-- trash icon --></svg>
</button>
<span id="delete-warning" class="sr-only">
This action cannot be undone
</span>3. Live Regions for Dynamic Content
<!-- Announce Claude Code responses -->
<div aria-live="polite" aria-atomic="true">
<p>Claude: I've updated the function with error handling.</p>
</div>
<!-- Urgent announcements -->
<div role="alert" aria-live="assertive">
Error: API rate limit exceeded
</div>🔍 Claude Code Terminal Accessibility
Terminal Screen Reader Configuration
# Windows Terminal + NVDA
# Enable "Review Cursor follows System Caret"
# NVDA Menu → Preferences → Settings → Review Cursor
# macOS Terminal + VoiceOver
# Enable "Speak text under mouse after delay"
# System Preferences → Accessibility → VoiceOver → Verbosity
# Linux + Orca
# Enable "Speak text under mouse"
# Orca Preferences → Speech → Speak object under mouseAnnouncing Claude Code Actions
Claude Code should provide clear feedback for all actions:
// Example: Announcing file operations
async function editFile(path: string, content: string) {
console.log(`Editing ${path}...`);
try {
await fs.writeFile(path, content);
console.log(`✓ Successfully updated ${path}`);
} catch (error) {
console.error(`✗ Failed to update ${path}: ${error.message}`);
}
}📋 Testing with Screen Readers
NVDA Testing Checklist
-
Install NVDA (Free from nvaccess.org)
-
Key Commands:
NVDA + Space: Toggle focus/browse modeTab: Navigate interactive elementsH: Jump between headingsNVDA + F7: Elements list
-
Test Scenarios:
- Can navigate all interactive elements
- Form labels are announced correctly
- Dynamic updates are announced
- Error messages are perceivable
- Focus order is logical
VoiceOver Testing (macOS)
-
Enable VoiceOver:
Cmd + F5 -
Key Commands:
VO + A: Read from current positionVO + Left/Right: Navigate by elementTab: Navigate focusable elementsVO + U: Open rotor for navigation
-
Terminal Testing:
# Enable VoiceOver terminal support defaults write com.apple.Terminal AccessibilityEnabled -bool true
🎨 AI-Generated Code Accessibility
Ensuring Accessible Output
When Claude Code generates UI code, it should follow these patterns:
// ❌ Bad: Inaccessible generated code
function generateButton() {
return `<div class="btn" onclick="submit()">Click me</div>`;
}
// ✅ Good: Accessible generated code
function generateButton() {
return `
<button
type="button"
class="btn"
aria-label="Submit form"
onclick="submit()"
>
Submit
</button>
`;
}Common Accessibility Attributes to Include
const accessibilityChecklist = {
images: ['alt', 'role="img"'],
buttons: ['aria-label', 'aria-describedby', 'type'],
forms: ['label', 'aria-required', 'aria-invalid'],
regions: ['aria-label', 'role', 'aria-labelledby'],
dynamic: ['aria-live', 'aria-atomic', 'role="status"']
};🔧 Debugging Screen Reader Issues
Common Problems and Solutions
-
Elements Not Announced
- Check for
aria-hidden="true"ordisplay: none - Ensure proper semantic HTML
- Verify ARIA roles are valid
- Check for
-
Incorrect Reading Order
- Review DOM structure
- Check CSS positioning (floats, absolute)
- Use
aria-flowtofor complex layouts
-
Missing Context
- Add descriptive labels
- Use
aria-describedbyfor additional context - Group related elements with regions
Browser DevTools for Screen Reader Testing
// Chrome DevTools Console - Test ARIA
function checkAccessibility(selector) {
const element = document.querySelector(selector);
return {
role: element.getAttribute('role'),
label: element.getAttribute('aria-label'),
describedBy: element.getAttribute('aria-describedby'),
labelledBy: element.getAttribute('aria-labelledby'),
computedRole: element.computedRole,
computedName: element.computedName
};
}📊 Screen Reader Compatibility Matrix
| Feature | NVDA | JAWS | VoiceOver | Orca |
|---|---|---|---|---|
| ARIA Live Regions | ✅ | ✅ | ✅ | ✅ |
| Custom ARIA Roles | ✅ | ✅ | ⚠️ | ✅ |
| MathML | ✅ | ✅ | ❌ | ⚠️ |
| SVG with Title | ✅ | ✅ | ✅ | ⚠️ |
| CSS Generated Content | ⚠️ | ✅ | ✅ | ❌ |
✅ Full support | ⚠️ Partial support | ❌ No support
🚀 Best Practices for Claude Code
1. Announce Operation Status
// Provide clear feedback for all operations
class ClaudeCodeAccessible {
async executeCommand(command: string) {
this.announce(`Executing: ${command}`);
try {
const result = await this.runCommand(command);
this.announce(`Success: ${command} completed`);
return result;
} catch (error) {
this.announce(`Error: ${command} failed - ${error.message}`, 'alert');
throw error;
}
}
announce(message: string, priority = 'polite') {
// Terminal output for screen readers
console.log(`[${new Date().toISOString()}] ${message}`);
// If in browser context, use ARIA live regions
if (typeof document !== 'undefined') {
const liveRegion = document.querySelector(`[aria-live="${priority}"]`);
if (liveRegion) {
liveRegion.textContent = message;
}
}
}
}2. Structure Output for Clarity
// Format Claude Code responses for screen readers
function formatResponse(response: ClaudeResponse) {
return `
=== Claude Code Response ===
Status: ${response.status}
Files Modified: ${response.files.length}
${response.files.map(file => `
- ${file.path}
Action: ${file.action}
Lines changed: ${file.linesChanged}
`).join('\n')}
Summary: ${response.summary}
=== End of Response ===
`.trim();
}📚 Resources and Tools
Testing Tools
Learning Resources
🎯 Key Takeaways
- Always test with real screen readers - Automated tools catch only ~30% of issues
- Use semantic HTML first - ARIA should enhance, not replace semantic markup
- Provide context and feedback - Screen reader users need clear status updates
- Structure content logically - Reading order should make sense
- Test early and often - Accessibility issues compound over time
🧭 Quick Navigation
← Back to Accessibility | Keyboard Navigation → | Voice Control →