|
| 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 | + |
| 18 | +- `types`: `["issue"]` |
| 19 | +- `includePreservedMessages`: `true` (to catch issues that occurred during page load) |
| 20 | + |
| 21 | +This often reveals missing labels, invalid ARIA attributes, and other critical errors without manual investigation. |
| 22 | + |
| 23 | +### 2. Semantics & Structure |
| 24 | + |
| 25 | +The accessibility tree exposes the heading hierarchy and semantic landmarks. |
| 26 | + |
| 27 | +1. Navigate to the page. |
| 28 | +2. Use `take_snapshot` to capture the accessibility tree. |
| 29 | +3. **Check Heading Levels**: Ensure heading levels (`h1`, `h2`, `h3`, etc.) are logical and do not skip levels. The snapshot will include heading roles. |
| 30 | +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. |
| 31 | + |
| 32 | +### 3. Labels, Forms & Text Alternatives |
| 33 | + |
| 34 | +1. Locate buttons, inputs, and images in the `take_snapshot` output. |
| 35 | +2. Ensure interactive elements have an accessible name (e.g., a button should not just say `""` if it only contains an icon). |
| 36 | +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`: |
| 37 | + ```javascript |
| 38 | + () => { |
| 39 | + const inputs = Array.from( |
| 40 | + document.querySelectorAll('input, select, textarea'), |
| 41 | + ); |
| 42 | + return inputs |
| 43 | + .filter(i => { |
| 44 | + const hasId = i.id && document.querySelector(`label[for="${i.id}"]`); |
| 45 | + const hasAria = |
| 46 | + i.getAttribute('aria-label') || i.getAttribute('aria-labelledby'); |
| 47 | + const hasImplicitLabel = i.closest('label'); |
| 48 | + return !hasId && !hasAria && !hasImplicitLabel; |
| 49 | + }) |
| 50 | + .map(i => ({ |
| 51 | + tag: i.tagName, |
| 52 | + id: i.id, |
| 53 | + name: i.name, |
| 54 | + placeholder: i.placeholder, |
| 55 | + })); |
| 56 | + }; |
| 57 | + ``` |
| 58 | + |
| 59 | +```` |
| 60 | + |
| 61 | +4. Check images for `alt` text. |
| 62 | + |
| 63 | +### 4. Focus & Keyboard Navigation |
| 64 | + |
| 65 | +Testing "keyboard traps" and proper focus management without visual feedback relies on tracking the focused element. |
| 66 | + |
| 67 | +1. Use the `press_key` tool with `"Tab"` or `"Shift+Tab"` to move focus. |
| 68 | +2. Use `take_snapshot` to capture the updated accessibility tree. |
| 69 | +3. Locate the element marked as focused in the snapshot to verify focus moved to the expected interactive element. |
| 70 | +4. If a modal opens, focus must move into the modal and "trap" within it until closed. |
| 71 | + |
| 72 | +### 5. Tap Targets and Visuals |
| 73 | + |
| 74 | +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`: |
| 75 | +
|
| 76 | +```javascript |
| 77 | +// Usage in console: copy, paste, and call with element: fn(element) |
| 78 | +el => { |
| 79 | + const rect = el.getBoundingClientRect(); |
| 80 | + return {width: rect.width, height: rect.height}; |
| 81 | +}; |
| 82 | +```` |
| 83 | +
|
| 84 | +_Pass the element's `uid` from the snapshot as an argument to the tool._ |
| 85 | + |
| 86 | +### 6. Color Contrast |
| 87 | + |
| 88 | +To verify color contrast ratios, start by checking for native accessibility issues: |
| 89 | + |
| 90 | +1. Call `list_console_messages` with `types: ["issue"]`. |
| 91 | +2. Look for "Low Contrast" issues in the output. |
| 92 | + |
| 93 | +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. |
| 94 | + |
| 95 | +**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`. |
| 96 | + |
| 97 | +```javascript |
| 98 | +el => { |
| 99 | + function getRGB(colorStr) { |
| 100 | + const match = colorStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); |
| 101 | + return match |
| 102 | + ? [parseInt(match[1]), parseInt(match[2]), parseInt(match[3])] |
| 103 | + : [255, 255, 255]; |
| 104 | + } |
| 105 | + function luminance(r, g, b) { |
| 106 | + const a = [r, g, b].map(function (v) { |
| 107 | + v /= 255; |
| 108 | + return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); |
| 109 | + }); |
| 110 | + return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722; |
| 111 | + } |
| 112 | +
|
| 113 | + const style = window.getComputedStyle(el); |
| 114 | + const fg = getRGB(style.color); |
| 115 | + let bg = getRGB(style.backgroundColor); |
| 116 | +
|
| 117 | + // Basic contrast calculation (Note: Doesn't account for transparency over background images) |
| 118 | + const l1 = luminance(fg[0], fg[1], fg[2]); |
| 119 | + const l2 = luminance(bg[0], bg[1], bg[2]); |
| 120 | + const ratio = (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05); |
| 121 | +
|
| 122 | + return { |
| 123 | + color: style.color, |
| 124 | + bg: style.backgroundColor, |
| 125 | + contrastRatio: ratio.toFixed(2), |
| 126 | + }; |
| 127 | +}; |
| 128 | +``` |
| 129 | + |
| 130 | +_Pass the element's `uid` to test the contrast against WCAG AA (4.5:1 for normal text, 3:1 for large text)._ |
| 131 | +
|
| 132 | +### 7. Global Page Checks |
| 133 | +
|
| 134 | +Verify document-level accessibility settings often missed in component testing: |
| 135 | +
|
| 136 | +```javascript |
| 137 | +(() => { |
| 138 | + const f = () => { |
| 139 | + return { |
| 140 | + lang: |
| 141 | + document.documentElement.lang || |
| 142 | + 'MISSING - Screen readers need this for pronunciation', |
| 143 | + title: document.title || 'MISSING - Required for context', |
| 144 | + viewport: |
| 145 | + document.querySelector('meta[name="viewport"]')?.content || |
| 146 | + 'MISSING - Check for user-scalable=no (bad practice)', |
| 147 | + reducedMotion: window.matchMedia('(prefers-reduced-motion: reduce)') |
| 148 | + .matches |
| 149 | + ? 'Enabled' |
| 150 | + : 'Disabled', |
| 151 | + }; |
| 152 | + }; |
| 153 | + try { |
| 154 | + console.log(f()); |
| 155 | + } catch (e) {} // Log for manual console usage |
| 156 | + return f; |
| 157 | +})(); |
| 158 | +``` |
| 159 | +
|
| 160 | +## Troubleshooting |
| 161 | +
|
| 162 | +If standard a11y queries fail or the `evaluate_script` snippets return unexpected results: |
| 163 | +
|
| 164 | +- **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