Debugging and Troubleshooting
1. Browser DevTools for Accessibility
| Browser | DevTools Feature | Access Method | Key Capabilities |
|---|---|---|---|
| Chrome/Edge | Accessibility Panel | DevTools → Elements → Accessibility tab | Accessibility tree, ARIA properties, computed properties, contrast checker |
| Chrome | Lighthouse | DevTools → Lighthouse → Accessibility category | Automated audit with actionable suggestions, performance scoring |
| Chrome | CSS Overview | DevTools → More tools → CSS Overview | Contrast issues detection across entire page |
| Chrome | Rendering Panel | DevTools → More tools → Rendering | Emulate vision deficiencies, prefers-reduced-motion, prefers-color-scheme, forced colors |
| Firefox | Accessibility Inspector | DevTools → Accessibility | Full accessibility tree, relationship visualization, simulation tools, audit |
| Firefox | Accessibility Picker | Accessibility Inspector → Pick element | Click to inspect element's a11y properties, keyboard navigation path |
| Safari | Accessibility Audit | Develop → Show Web Inspector → Audit tab | Built-in accessibility audit with detailed issues |
| Safari | Element Details | Inspector → Node → Accessibility | Computed role, label, value, accessibility tree |
Example: Using Chrome DevTools Accessibility Panel
// Steps to debug accessibility in Chrome:
1. Open DevTools (F12 or Cmd+Option+I)
2. Select Elements tab
3. Choose an element in the DOM tree
4. Click "Accessibility" tab in the right panel
// What you can inspect:
- Computed Properties: role, name, description
- ARIA Attributes: all aria-* values
- Accessibility Tree: hierarchical view
- Source order vs. visual order
- Contrast ratio for text elements
// Using Lighthouse for automated testing:
1. DevTools → Lighthouse tab
2. Select "Accessibility" category
3. Choose device (Mobile/Desktop)
4. Click "Generate report"
// Results include:
- Score (0-100)
- Passed audits
- Failed audits with elements affected
- Manual checks to perform
- Detailed documentation links
Example: Firefox Accessibility Inspector Features
// Advanced features in Firefox DevTools:
1. Accessibility Panel:
- Full accessibility tree view
- Shows all ARIA roles and properties
- Relationship arrows (labelledby, describedby, controls)
- Tabbing order visualization
2. Simulation Tools:
- Simulate: Click "Simulate" dropdown
- Options: None, Protanopia, Deuteranopia, Tritanopia,
Achromatopsia, Contrast loss
3. Keyboard Navigation Checker:
- Shows tab order with numbers
- Highlights focusable elements
- Identifies keyboard traps
4. Check for Issues:
- Click "Check for issues" button
- Filters: All issues, Contrast, Keyboard, Text labels
- Shows count and severity
DevTools Tips:
- Chrome: Use Rendering panel to test prefers-reduced-motion, prefers-color-scheme, forced-colors-mode
- Firefox: Best accessibility tree visualization with relationship arrows
- Safari: Most accurate for testing on macOS with VoiceOver integration
- Contrast Checker: All browsers show contrast ratio - aim for 4.5:1 (text) or 3:1 (large text)
- Accessibility Tree: Shows what screen readers actually see (different from DOM tree)
- Emulation: Test color blindness, reduced motion, dark mode without changing system settings
2. Screen Reader Debug Techniques
| Screen Reader | Debug Technique | Keyboard Shortcut | What to Check |
|---|---|---|---|
| NVDA (Windows) | Speech Viewer | NVDA Menu → Tools → Speech Viewer | Visual display of everything NVDA announces - verify announcements without audio |
| NVDA | Element List | NVDA+F7 | Lists landmarks, headings, links, form fields - verify structure |
| NVDA | Browse/Focus Mode | NVDA+Space (toggle) | Browse mode for reading, Focus mode for forms - verify mode switching |
| JAWS (Windows) | Virtual Viewer | Insert+Spacebar, V | Shows virtual buffer content as JAWS sees it |
| JAWS | Forms Mode | Enter/Esc (auto switches) | Verify forms mode activates correctly for interactive elements |
| JAWS | Quick Navigation | H (headings), K (links), F (form fields) | Navigate by element type - verify all elements are reachable |
| VoiceOver (macOS) | Rotor | VO+U | Lists landmarks, headings, links, form controls - verify organization |
| VoiceOver | Item Chooser | VO+I | Search for specific text or elements - verify labeling |
| VoiceOver | Web Rotor | VO+U (on web content) | Navigate by landmarks, headings, links, tables, form controls |
| TalkBack (Android) | Reading Controls | Swipe up/down with one finger | Change navigation granularity - verify content structure |
| Narrator (Windows) | Scan Mode | Narrator+Space (toggle) | Browse vs interact mode - verify mode transitions |
Example: NVDA Testing Workflow
// Essential NVDA shortcuts for debugging:
NVDA+N // Open NVDA menu
NVDA+Q // Quit NVDA
NVDA+T // Read title
NVDA+B // Read status bar
// Navigation:
H / Shift+H // Next/Previous heading
K / Shift+K // Next/Previous link
F / Shift+F // Next/Previous form field
B / Shift+B // Next/Previous button
D / Shift+D // Next/Previous landmark
1-6 / Shift+1-6 // Next/Previous heading level
// Reading:
Insert+↓ // Say all (read from current position)
NVDA+F7 // Element list (landmarks, headings, links, etc.)
NVDA+Space // Toggle browse/focus mode
// Debugging specific elements:
NVDA+Tab // Report current element (role, name, state)
Insert+F // Report formatting
NVDA+F // Find text on page
// Testing live regions:
NVDA+Shift+R // Toggle between live region verbosity modes
NVDA+5 (numpad) // Report current location
// Common issues to check:
1. Speech Viewer shows actual announcements
2. Browse mode works for static content
3. Focus mode activates for form controls
4. All interactive elements announced correctly
5. ARIA live regions announce changes
6. No unexpected announcement duplication
Example: VoiceOver Testing Workflow
// Essential VoiceOver shortcuts for debugging:
// (VO = Control+Option)
VO+F5 // Start VoiceOver
VO+F8 // Open VoiceOver Utility
Cmd+F5 // Toggle VoiceOver on/off
// Navigation:
VO+→ // Move to next item
VO+← // Move to previous item
VO+Shift+↓ // Enter group/container
VO+Shift+↑ // Exit group/container
// Web Rotor (navigate by element type):
VO+U // Open rotor
← / → // Switch category (headings, links, landmarks, etc.)
↓ / ↑ // Select item in category
Enter // Navigate to selected item
// Reading:
VO+A // Read from current position
VO+Shift+U // Open Item Chooser (search)
// Debugging:
VO+F3 // Read caption (accessibility label)
VO+F4 // Read hint
VO+Shift+H // Hear element description
VO+J // Jump to linked item
// Verbosity settings:
VO+V // Open Verbosity menu
// Adjust announcement detail level
// Testing checklist:
1. All elements reachable with VO+→
2. Rotor shows proper structure (headings, landmarks)
3. Forms mode activates for inputs
4. Buttons announce role and state
5. Live regions announce updates
6. Table headers announced with cells
7. Image alt text read correctly
Screen Reader Testing Gotchas: Different screen readers interpret ARIA differently. NVDA
announces aria-label, JAWS prefers visible text. Live regions work inconsistently - test all SRs. Browse/Focus
mode transitions vary. Virtual buffer can cache old content - refresh with F5. Screen readers may announce in
unexpected order - use DevTools accessibility tree to verify. Always test with actual users when possible.
3. Common Accessibility Issues and Fixes
| Common Issue | Symptom | Quick Fix | WCAG Violation |
|---|---|---|---|
| Missing Alt Text | Images announced as filename or "image" | Add alt="" for decorative, descriptive text for meaningful images |
1.1.1 Non-text Content |
| Poor Color Contrast | Text hard to read against background | Use contrast checker tool; aim for 4.5:1 (normal text) or 3:1 (large text/UI) | 1.4.3 Contrast (Minimum) |
| Unlabeled Form Inputs | Screen reader announces "Edit, blank" without context | Add <label for="id"> or aria-label; ensure visible label exists | 1.3.1 Info and Relationships, 3.3.2 Labels |
| Keyboard Trap | Cannot Tab out of modal/widget | Implement focus trap with Esc key exit; manage focus on close | 2.1.2 No Keyboard Trap |
| Missing Focus Indicators | Can't see which element is focused | Never use outline: none without custom replacement; ensure 3:1 contrast |
2.4.7 Focus Visible |
| Incorrect Heading Order | Skip from H1 to H3; multiple H1s; headings out of order | Use sequential heading levels (H1 → H2 → H3); single H1 per page | 1.3.1 Info and Relationships, 2.4.6 Headings |
| Button with Div/Span | <div onclick> not keyboard accessible or announced as button | Use <button> element; if unavoidable, add role="button" and tabindex="0" | 4.1.2 Name, Role, Value |
| Empty Links/Buttons | Announced as "Link" or "Button" without purpose | Add visible text, aria-label, or sr-only text; never leave empty | 2.4.4 Link Purpose, 4.1.2 Name, Role, Value |
| Auto-playing Media | Video/audio starts automatically, interferes with screen reader | Remove autoplay; provide play/pause controls; respect prefers-reduced-motion | 1.4.2 Audio Control, 2.2.2 Pause, Stop, Hide |
| Tables for Layout | Data announced as table when it's just visual layout | Use CSS Grid/Flexbox for layout; reserve <table> for tabular data only | 1.3.1 Info and Relationships |
| Missing Language Attribute | Screen reader uses wrong pronunciation/voice | Add lang="en" to <html>; use lang on elements with different languages |
3.1.1 Language of Page, 3.1.2 Language of Parts |
| Non-descriptive Link Text | "Click here" or "Read more" without context | Use descriptive link text; add aria-label with context; avoid generic phrases | 2.4.4 Link Purpose |
Example: Quick Fixes Cheat Sheet
// Missing alt text - FIX:
<!-- Before: -->
<img src="logo.png">
<!-- After: -->
<img src="logo.png" alt="Company Name Logo">
<!-- Decorative: -->
<img src="decorative.png" alt="">
// Poor contrast - FIX:
/* Before: */
.text { color: #777; background: #fff; } /* 3.3:1 - FAIL */
/* After: */
.text { color: #595959; background: #fff; } /* 4.5:1 - PASS */
// Unlabeled input - FIX:
<!-- Before: -->
<input type="text" placeholder="Enter name">
<!-- After: -->
<label for="name">Name:</label>
<input type="text" id="name" placeholder="e.g., John Smith">
// Non-semantic button - FIX:
<!-- Before: -->
<div onclick="submit()">Submit</div>
<!-- After: -->
<button type="button" onclick="submit()">Submit</button>
// Missing focus indicator - FIX:
/* Before: */
button:focus { outline: none; } /* BAD */
/* After: */
button:focus-visible {
outline: 2px solid #005fcc;
outline-offset: 2px;
}
// Incorrect heading order - FIX:
<!-- Before: -->
<h1>Page Title</h1>
<h3>Section</h3> <!-- Skipped H2 -->
<!-- After: -->
<h1>Page Title</h1>
<h2>Section</h2>
<h3>Subsection</h3>
// Empty link - FIX:
<!-- Before: -->
<a href="/profile"><img src="user.png"></a>
<!-- After: -->
<a href="/profile">
<img src="user.png" alt="View profile">
</a>
<!-- Or: -->
<a href="/profile" aria-label="View profile">
<img src="user.png" aria-hidden="true">
</a>
// Non-descriptive link - FIX:
<!-- Before: -->
<a href="/article1">Read more</a>
<!-- After: -->
<a href="/article1">Read more about Web Accessibility</a>
<!-- Or: -->
<a href="/article1" aria-label="Read more about Web Accessibility">
Read more
</a>
Debugging Workflow:
- Automated scan: Run Lighthouse, axe DevTools, or WAVE to catch obvious issues
- Keyboard test: Navigate entire page with Tab/Shift+Tab, Enter, Spacebar, arrows - no mouse
- Screen reader test: Test with NVDA (Windows), VoiceOver (Mac), or TalkBack (Android)
- Visual inspection: Check contrast, focus indicators, zoom to 200%, test dark mode
- Manual testing: Follow WCAG checklist for manual checks (color not sole indicator, etc.)
- User testing: Get feedback from actual users with disabilities when possible
4. ARIA Implementation Debugging
| ARIA Issue | How to Detect | Common Cause | Solution |
|---|---|---|---|
| ARIA not announced | Screen reader ignores aria-label or aria-describedby | Used on non-labelable element (div, span without role) | Add appropriate role or use semantic HTML; verify in accessibility tree |
| Conflicting roles | Element announced incorrectly or multiple times | Native role conflicts with ARIA role (e.g., role="button" on <button>) | Remove redundant ARIA; prefer semantic HTML over ARIA |
| Invalid ARIA reference | aria-labelledby or aria-describedby silent | Referenced ID doesn't exist or typo in ID | Verify IDs exist and match exactly (case-sensitive); check in DevTools |
| Missing required ARIA | Widget doesn't function properly with screen reader | Role requires specific attributes (e.g., tablist needs aria-selected) | Check ARIA APG for required attributes; validate with axe or WAVE |
| Incorrect aria-live | Updates not announced or announced too much | Wrong politeness level or aria-atomic setting | Use polite for most cases; assertive sparingly; test with SR |
| Dynamic ARIA not updating | State changes not reflected to screen reader | ARIA attributes not updated when state changes | Update aria-expanded, aria-selected, etc. in JavaScript; verify in DevTools |
| aria-hidden issues | Important content not announced or decorative content announced | aria-hidden="true" on focusable elements or critical content | Never hide focusable elements; ensure visible text isn't hidden |
| Redundant ARIA | Duplicate announcements or verbose output | Both aria-label and visible label present | Choose one labeling method; use aria-labelledby for visible label |
Example: Debugging ARIA with DevTools
// Check accessibility tree in Chrome DevTools:
// 1. Select element in Elements panel
// 2. Open Accessibility tab
// 3. Look for:
Computed Properties:
Name: "Submit form" // From aria-label, label, or content
Role: "button" // Semantic or ARIA role
Description: "Save changes" // From aria-describedby
// Common issues to look for:
// Issue: Name is empty
<button></button>
// Fix: Add text content or aria-label
<button>Submit</button>
<button aria-label="Submit form"><span aria-hidden="true">✓</span></button>
// Issue: Role is "generic" when it should be specific
<div onclick="doSomething()">Click me</div>
// Fix: Add role and make keyboard accessible
<div role="button" tabindex="0" onclick="doSomething()">Click me</div>
// Better: Use semantic HTML
<button onclick="doSomething()">Click me</button>
// Issue: aria-labelledby references non-existent ID
<button aria-labelledby="submit-label">Send</button>
// No element with id="submit-label" exists
// Fix: Create the element or remove the attribute
<span id="submit-label">Submit form</span>
<button aria-labelledby="submit-label">Send</button>
// Issue: Required ARIA attributes missing
<div role="tab">Tab 1</div>
// Missing: aria-selected, aria-controls
// Fix: Add required attributes
<div role="tab"
aria-selected="true"
aria-controls="panel1"
tabindex="0">Tab 1</div>
// Validate ARIA with axe DevTools:
const axe = require('axe-core');
axe.run(document, {
rules: {
'aria-allowed-attr': { enabled: true },
'aria-required-attr': { enabled: true },
'aria-required-children': { enabled: true },
'aria-required-parent': { enabled: true },
'aria-valid-attr': { enabled: true },
'aria-valid-attr-value': { enabled: true }
}
}, (err, results) => {
console.log(results.violations);
});
Example: Common ARIA Mistakes and Fixes
// MISTAKE 1: aria-label on div without role
<div aria-label="Important message">Content</div>
// FIX: Add appropriate role or use semantic element
<div role="region" aria-label="Important message">Content</div>
<section aria-label="Important message">Content</section>
// MISTAKE 2: Hiding focusable element
<button aria-hidden="true">Click me</button>
// FIX: Remove aria-hidden or make non-focusable
<button>Click me</button>
<span aria-hidden="true">Decorative icon</span>
// MISTAKE 3: Using role on semantic element unnecessarily
<button role="button">Submit</button>
<nav role="navigation">...</nav>
// FIX: Remove redundant role
<button>Submit</button>
<nav>...</nav>
// MISTAKE 4: Invalid ARIA attribute value
<button aria-pressed="yes">Toggle</button>
// FIX: Use valid value (true/false/mixed)
<button aria-pressed="true">Toggle</button>
// MISTAKE 5: Missing required children
<div role="list">
<div>Item 1</div>
<div>Item 2</div>
</div>
// FIX: Add required child role
<div role="list">
<div role="listitem">Item 1</div>
<div role="listitem">Item 2</div>
</div>
// Or use semantic HTML
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
// MISTAKE 6: Live region not in DOM on load
// Wrong: Creating live region dynamically
function announce(message) {
const live = document.createElement('div');
live.setAttribute('role', 'status');
live.textContent = message;
document.body.appendChild(live);
}
// FIX: Create live region once on page load
<div role="status" aria-live="polite" class="sr-only"></div>
function announce(message) {
document.querySelector('[role="status"]').textContent = message;
}
// MISTAKE 7: Not updating dynamic ARIA
<button aria-expanded="false" onclick="toggle()">Menu</button>
function toggle() {
menu.hidden = !menu.hidden;
// Missing: Update aria-expanded
}
// FIX: Update ARIA attribute
function toggle() {
const isExpanded = menu.hidden;
menu.hidden = !menu.hidden;
button.setAttribute('aria-expanded', isExpanded);
}
ARIA First Rule: "No ARIA is better than bad ARIA." Use semantic HTML first. Add ARIA only when
HTML can't express the semantics. Test extensively with screen readers - behavior varies. Validate with
automated tools (axe, WAVE, Lighthouse). Check accessibility tree in DevTools. Read ARIA APG for correct
patterns.
5. Cross-Browser Compatibility Testing
| Test Scenario | Browsers to Test | Known Issues | Testing Strategy |
|---|---|---|---|
| Screen Reader + Browser | JAWS+Chrome, NVDA+Firefox, VoiceOver+Safari, Narrator+Edge, TalkBack+Chrome | ARIA support varies; live regions inconsistent; focus management differs | Test primary SR+browser combo for your region; document known issues |
| Keyboard Navigation | Chrome, Firefox, Safari, Edge | Safari Tab behavior different (requires enabling); Focus order can vary | Test Tab, Shift+Tab, Enter, Space, arrows in all browsers |
| Focus Indicators | All major browsers | Default indicators differ; :focus-visible support varies (older browsers) | Test with Tab navigation; verify 3:1 contrast in all browsers |
| Form Validation | Chrome, Firefox, Safari, Edge | HTML5 validation messages vary; required attribute styling differs | Test with SR; verify error announcement; check visual indicators |
| ARIA Live Regions | Test with each SR+browser combo | Politeness levels interpreted differently; atomic behavior varies | Test dynamic content updates; verify announcements in each SR |
| CSS Media Queries | Chrome, Firefox, Safari (desktop & mobile) | prefers-reduced-motion not supported in IE11; prefers-contrast limited support | Test with system settings; verify fallbacks for unsupported queries |
| Touch Targets (Mobile) | Chrome Android, Safari iOS, Samsung Internet | Touch target calculation differs; zoom behavior varies | Test on actual devices; verify 44×44px minimum; check spacing |
| Zoom and Reflow | All major browsers at 200%, 400% | Text may not scale properly; horizontal scroll may appear | Zoom to 200% and 400%; verify no horizontal scroll; content readable |
Example: Browser Compatibility Testing Checklist
// Essential Browser + Screen Reader Combinations:
Windows:
- NVDA + Firefox (most common free combination)
- JAWS + Chrome (enterprise standard)
- Narrator + Edge (built-in Windows)
macOS:
- VoiceOver + Safari (most accurate pairing)
- VoiceOver + Chrome (secondary testing)
iOS:
- VoiceOver + Safari (mobile)
Android:
- TalkBack + Chrome (mobile)
// Testing workflow:
1. Automated testing (same across browsers):
- Run Lighthouse in Chrome
- Run axe DevTools extension
- Validate HTML with W3C Validator
2. Keyboard testing (each browser):
✓ Tab through entire page
✓ Shift+Tab reverses correctly
✓ Enter activates links/buttons
✓ Space activates buttons/toggles
✓ Arrow keys work in custom widgets
✓ Esc closes modals/menus
✓ No keyboard traps
3. Screen reader testing (each combination):
✓ Page structure (headings, landmarks)
✓ All content reachable
✓ Forms properly labeled
✓ Error messages announced
✓ Dynamic content updates announced
✓ Images have appropriate alt text
✓ Tables announce headers
4. Visual testing (each browser):
✓ Focus indicators visible (3:1 contrast)
✓ Text contrast meets WCAG AA
✓ Zoom to 200% - no horizontal scroll
✓ Dark mode works correctly
✓ High contrast mode (Windows)
5. Mobile testing (iOS + Android):
✓ Touch targets 44×44px minimum
✓ Pinch zoom enabled
✓ Screen reader gestures work
✓ Content reflows for small screens
✓ No reliance on hover
Example: Known Browser Differences to Test
// Safari specific:
// Enable Tab navigation: Safari Preferences → Advanced →
// "Press Tab to highlight each item on a webpage"
// Test in Safari specifically:
- Tab behavior different from other browsers
- Form validation messages styled differently
- VoiceOver integration most accurate with Safari
- Some ARIA features work only in Safari+VO
// Firefox specific:
// Best accessibility DevTools
- Most standards-compliant ARIA implementation
- Preferred for NVDA testing
- Focus management most predictable
// Chrome/Edge specific:
// Chromium-based - similar behavior
- Best Lighthouse integration
- Most common browser globally
- Test with JAWS (common enterprise SR)
// Mobile Safari (iOS):
// VoiceOver built-in
- Swipe right/left to navigate
- Two-finger scroll
- Rotor gestures (VO+U equivalent)
- Test zoom and reflow
// Chrome Android:
// TalkBack built-in on most Android
- Swipe right/left to navigate
- Explore by touch
- Volume key shortcuts
- Test with different screen sizes
// Feature support differences:
// Always check caniuse.com for:
const features = {
'focus-visible': 'Safari partial support',
'prefers-reduced-motion': 'IE11 not supported',
'prefers-color-scheme': 'IE11 not supported',
'prefers-contrast': 'Limited support',
'inert': 'Chrome 102+, Safari 15.5+',
'aria-description': 'Chrome 83+, limited SR support'
};
// Provide fallbacks:
@media (prefers-reduced-motion: reduce) {
* { animation: none !important; }
}
/* Fallback for older browsers */
.no-prefers-reduced-motion {
animation: none !important;
}
Cross-Browser Testing Best Practices:
- Prioritize: Test with most common SR+browser combo in your region (NVDA+Firefox in US/EU, JAWS+Chrome in enterprise)
- Document: Keep track of known issues and workarounds for each browser
- Automate: Use CI/CD with automated a11y tests (pa11y, axe-core, Lighthouse CI)
- Real Devices: Test mobile on actual devices, not just emulators
- Progressive Enhancement: Build for baseline, enhance for modern browsers
- Polyfills: Use for critical features (focus-visible, inert) in older browsers
- User Testing: Get feedback from real users with disabilities across different browsers/SRs
- Monitor: Track browser/SR usage in your analytics to prioritize testing
Debugging and Troubleshooting Summary
- DevTools: Chrome Lighthouse & Accessibility panel; Firefox best a11y inspector; Safari for VoiceOver testing
- Screen Readers: NVDA Speech Viewer for visual testing; VoiceOver Rotor for structure; test with actual SRs, not just DevTools
- Common Issues: Missing alt text, poor contrast, unlabeled inputs, keyboard traps, missing focus indicators - use automated tools first
- ARIA Debugging: Check accessibility tree in DevTools; validate with axe; never hide focusable elements; update dynamic ARIA
- Cross-Browser: Test NVDA+Firefox, JAWS+Chrome, VoiceOver+Safari; verify keyboard, SR, zoom, mobile; document known issues
- Testing Workflow: Automated scan → Keyboard test → Screen reader → Visual inspection → User testing
- Tools: Lighthouse, axe DevTools, WAVE, NVDA/JAWS/VoiceOver, browser DevTools, contrast checkers
- Best Practices: Test early and often; fix issues as you build; use semantic HTML first; validate with real users