|
| 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