| name | a11y-debugging |
| description | Uses Chrome DevTools MCP for accessibility (a11y) debugging and auditing based on web.dev guidelines. Use when testing semantic HTML, ARIA labels, focus states, keyboard navigation, tap targets, and color contrast. |
Core Concepts
Accessibility Tree vs DOM: Visually hiding an element (e.g., CSS opacity: 0) behaves differently for screen readers than display: none or aria-hidden="true". The take_snapshot tool returns the accessibility tree of the page, which represents what assistive technologies "see", making it the most reliable source of truth for semantic structure.
Reading web.dev documentation: If you need to research specific accessibility guidelines (like https://web.dev/articles/accessible-tap-targets), you can append .md.txt to the URL (e.g., https://web.dev/articles/accessible-tap-targets.md.txt) to fetch the clean, raw markdown version. This is much easier to read!
Workflow Patterns
1. Automated Audit (Lighthouse)
Start by running a Lighthouse accessibility audit to get a comprehensive baseline. This tool provides a high-level score and lists specific failing elements with remediation advice.
- Run the audit:
- Set
mode to "navigation" to refresh the page and capture load issues.
- Set
outputDirPath (e.g., /tmp/lh-report) to save the full JSON report.
- Analyze the Summary:
- Check
scores (0-1 scale). A score < 1 indicates violations.
- Review
audits.failed count.
- Review the Report (CRITICAL):
2. Browser Issues & Audits
Chrome automatically checks for common accessibility problems. Use list_console_messages to check for these native audits:
types: ["issue"]
includePreservedMessages: true (to catch issues that occurred during page load)
This often reveals missing labels, invalid ARIA attributes, and other critical errors without manual investigation.
3. Semantics & Structure
The accessibility tree exposes the heading hierarchy and semantic landmarks.
- Navigate to the page.
- Use
take_snapshot to capture the accessibility tree.
- Check Heading Levels: Ensure heading levels (
h1, h2, h3, etc.) are logical and do not skip levels. The snapshot will include heading roles.
- Content Reordering: Verify that the DOM order (which drives the accessibility tree) matches the visual reading order. Use
take_screenshot to inspect the visual layout and compare it against the snapshot structure to catch CSS floats or absolute positioning that jumbles the logical flow.
4. Labels, Forms & Text Alternatives
- Locate buttons, inputs, and images in the
take_snapshot output.
- Ensure interactive elements have an accessible name (e.g., a button should not just say
"" if it only contains an icon).
- Orphaned Inputs: Verify that all form inputs have associated labels. Use
evaluate_script to check for inputs missing id (for label[for]) or aria-label:
() =>
Array.from(document.querySelectorAll('input, select, textarea'))
.filter(i => {
const hasId = i.id && document.querySelector(`label[for="${i.id}"]`);
const hasAria =
i.getAttribute('aria-label') || i.getAttribute('aria-labelledby');
return !hasId && !hasAria && !i.closest('label');
})
.map(i => ({
tag: i.tagName,
id: i.id,
name: i.name,
placeholder: i.placeholder,
}));
4. Check images for `alt` text.
### 5. Focus & Keyboard Navigation
Testing "keyboard traps" and proper focus management without visual feedback relies on tracking the focused element.
1. Use the `press_key` tool with `"Tab"` or `"Shift+Tab"` to move focus.
2. Use `take_snapshot` to capture the updated accessibility tree.
3. Locate the element marked as focused in the snapshot to verify focus moved to the expected interactive element.
4. If a modal opens, focus must move into the modal and "trap" within it until closed.
### 6. Tap Targets and Visuals
According to web.dev, tap targets should be at least 48x48 pixels with sufficient spacing. Since the accessibility tree doesn't show sizes, use `evaluate_script`:
```js
// Usage in console: copy, paste, and call with element: fn(element)
el => {
const rect = el.getBoundingClientRect();
return {width: rect.width, height: rect.height};
};
Pass the element's uid from the snapshot as an argument to evaluate_script.
7. Color Contrast
To verify color contrast ratios, start by checking for native accessibility issues:
- Call
list_console_messages with types: ["issue"].
- Look for "Low Contrast" issues in the output.
If native audits do not report issues (which may happen in some headless environments) or if you need to check a specific element manually, you can use the following script as a fallback approximation.
Note: This script uses a simplified algorithm and may not account for transparency, gradients, or background images. For production-grade auditing, consider injecting axe-core.
el => {
function getRGB(colorStr) {
const match = colorStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
return match
? [parseInt(match[1]), parseInt(match[2]), parseInt(match[3])]
: [255, 255, 255];
}
function luminance(r, g, b) {
const a = [r, g, b].map(function (v) {
v /= 255;
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
});
return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
}
const style = window.getComputedStyle(el);
const fg = getRGB(style.color);
let bg = getRGB(style.backgroundColor);
const l1 = (fg[], fg[], fg[]);
l2 = (bg[], bg[], bg[]);
ratio = (.(l1, l2) + ) / (.(l1, l2) + );
{
: style.,
: style.,
: ratio.(),
};
};
Pass the element's uid to test the contrast against WCAG AA (4.5:1 for normal text, 3:1 for large text).
8. Global Page Checks
Verify document-level accessibility settings often missed in component testing:
() => ({
lang:
document.documentElement.lang ||
'MISSING - Screen readers need this for pronunciation',
title: document.title || 'MISSING - Required for context',
viewport:
document.querySelector('meta[name="viewport"]')?.content ||
'MISSING - Check for user-scalable=no (bad practice)',
reducedMotion: window.matchMedia('(prefers-reduced-motion: reduce)').matches
? 'Enabled'
: 'Disabled',
});
Troubleshooting
If standard a11y queries fail or the evaluate_script snippets return unexpected results:
- Visual Inspection: If automated scripts cannot determine contrast (e.g., text over gradient images or complex backgrounds), use
take_screenshot to capture the element. While models cannot measure exact contrast ratios from images, they can visually assess legibility and identifying obvious issues.