Skip to content

Commit 569dbe6

Browse files
committed
feat: add a new skill for accessibility debugging and auditing with Chrome DevTools MCP.
1 parent c9691c6 commit 569dbe6

1 file changed

Lines changed: 127 additions & 0 deletions

File tree

skills/a11y-debugging/SKILL.md

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
---
2+
name: a11y-debugging
3+
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.
4+
---
5+
6+
## Core Concepts
7+
8+
**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.
9+
10+
**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 using the `read_url_content` tool!
11+
12+
## Workflow Patterns
13+
14+
### 1. Browser Issues & Audits
15+
16+
Chrome automatically checks for common accessibility problems. Use `list_console_messages` to check for these native audits first:
17+
- `types`: `["issue"]`
18+
- `includePreservedMessages`: `true` (to catch issues that occurred during page load)
19+
20+
This often reveals missing labels, invalid ARIA attributes, and other critical errors without manual investigation.
21+
22+
### 2. Semantics & Structure
23+
24+
The accessibility tree exposes the heading hierarchy and semantic landmarks.
25+
26+
1. Navigate to the page.
27+
2. Use `take_snapshot` to capture the accessibility tree.
28+
3. **Check Heading Levels**: Ensure heading levels (`h1`, `h2`, `h3`, etc.) are logical and do not skip levels. The snapshot will include heading roles.
29+
4. **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.
30+
31+
### 3. Labels, Forms & Text Alternatives
32+
33+
1. Locate buttons, inputs, and images in the `take_snapshot` output.
34+
2. Ensure interactive elements have an accessible name (e.g., a button should not just say `""` if it only contains an icon).
35+
3. **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`:
36+
```javascript
37+
() => {
38+
const inputs = Array.from(document.querySelectorAll('input, select, textarea'));
39+
return inputs.filter(i => {
40+
const hasId = i.id && document.querySelector(`label[for="${i.id}"]`);
41+
const hasAria = i.getAttribute('aria-label') || i.getAttribute('aria-labelledby');
42+
const hasImplicitLabel = i.closest('label');
43+
return !hasId && !hasAria && !hasImplicitLabel;
44+
}).map(i => ({ tag: i.tagName, id: i.id, name: i.name, placeholder: i.placeholder }));
45+
}
46+
```
47+
4. Check images for `alt` text.
48+
49+
### 4. Focus & Keyboard Navigation
50+
51+
Testing "keyboard traps" and proper focus management without visual feedback relies on precise scripting.
52+
53+
1. Use `evaluate_script` to find the currently focused element:
54+
```javascript
55+
() => {
56+
const active = document.activeElement;
57+
return { tag: active.tagName, id: active.id, className: active.className, text: active.innerText };
58+
}
59+
```
60+
2. Use the `press_key` tool with `"Tab"` or `"Shift+Tab"` to move focus.
61+
3. Re-run the script in step 1 to ensure focus moved to the expected next interactive element.
62+
4. If a modal opens, focus must move into the modal and "trap" within it until closed.
63+
64+
### 5. Tap Targets and Visuals
65+
66+
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`:
67+
68+
```javascript
69+
(el) => {
70+
const rect = el.getBoundingClientRect();
71+
return { width: rect.width, height: rect.height };
72+
}
73+
```
74+
*Pass the element's `uid` from the snapshot as an argument to the tool.*
75+
76+
### 6. Color Contrast
77+
78+
To verify color contrast ratios without the DevTools UI, use `evaluate_script` to compute the relative luminance of the text (`color`) and background (`backgroundColor`).
79+
80+
```javascript
81+
(el) => {
82+
function getRGB(colorStr) {
83+
const match = colorStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
84+
return match ? [parseInt(match[1]), parseInt(match[2]), parseInt(match[3])] : [255, 255, 255];
85+
}
86+
function luminance(r, g, b) {
87+
const a = [r, g, b].map(function (v) {
88+
v /= 255;
89+
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
90+
});
91+
return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
92+
}
93+
94+
const style = window.getComputedStyle(el);
95+
const fg = getRGB(style.color);
96+
let bg = getRGB(style.backgroundColor);
97+
98+
// Basic contrast calculation (Note: Doesn't account for transparency over background images)
99+
const l1 = luminance(fg[0], fg[1], fg[2]);
100+
const l2 = luminance(bg[0], bg[1], bg[2]);
101+
const ratio = (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
102+
103+
return { color: style.color, bg: style.backgroundColor, contrastRatio: ratio.toFixed(2) };
104+
}
105+
```
106+
*Pass the element's `uid` to test the contrast against WCAG AA (4.5:1 for normal text, 3:1 for large text).*
107+
108+
### 7. Global Page Checks
109+
110+
Verify document-level accessibility settings often missed in component testing:
111+
112+
```javascript
113+
() => {
114+
return {
115+
lang: document.documentElement.lang || 'MISSING - Screen readers need this for pronunciation',
116+
title: document.title || 'MISSING - Required for context',
117+
viewport: document.querySelector('meta[name="viewport"]')?.content || 'MISSING - Check for user-scalable=no (bad practice)',
118+
reducedMotion: window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'Enabled' : 'Disabled'
119+
};
120+
}
121+
```
122+
123+
## Troubleshooting
124+
125+
If standard a11y queries fail or the `evaluate_script` snippets return unexpected results:
126+
127+
* **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.

0 commit comments

Comments
 (0)