Accessibility debugging via Chrome DevTools MCP. Lighthouse a11y audit, accessibility tree (take_snapshot), heading hierarchy, ARIA labels, focus/keyboard nav, color contrast, tap targets. WCAG 2.1 AA. Sources: ChromeDevTools/chrome-devtools-mcp (Apache-2.0).
Scanned 9/9/2026
Install to Claude Code
npx -y skills add yanacuti1121/Yana-AI --skill chrome-devtools-a11y-audit --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Chrome Devtools A11y Audit?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/yanacuti1121-chrome-devtools-a11y-audit-yana-ai)More formats (shields.io, HTML) on the badges page.
---
name: chrome-devtools-a11y-audit
description: Accessibility debugging via Chrome DevTools MCP. Lighthouse a11y audit, accessibility tree (take_snapshot), heading hierarchy, ARIA labels, focus/keyboard nav, color contrast, tap targets. WCAG 2.1 AA. Sources: ChromeDevTools/chrome-devtools-mcp (Apache-2.0).
origin: yana-ai — synthesized from ChromeDevTools/chrome-devtools-mcp (Apache-2.0)
license: Apache-2.0
version: 1.0.0
compatibility: yana-ai >= 1.3.55
---
# /chrome-devtools-a11y-audit
## When to Use
- Auditing a page for WCAG 2.1 AA compliance
- Finding missing ARIA labels, orphaned inputs, or broken heading hierarchy
- Testing keyboard navigation and focus management
- Checking color contrast and tap target sizes
## Do NOT use for
- Performance issues (use [[chrome-devtools-lcp-debug]])
- Memory leaks (use [[chrome-devtools-memory-leak]])
- Visual regression testing
---
## Key concept: accessibility tree vs DOM
```
DOM visibility (CSS opacity:0) ≠ screen-reader visibility.
opacity:0 → still in accessibility tree (screen readers see it)
display:none → removed from accessibility tree
aria-hidden="true" → removed from accessibility tree
take_snapshot returns the ACCESSIBILITY TREE — what assistive technologies see.
This is the most reliable source of truth for semantic structure.
```
---
## Step 1: Lighthouse automated audit
```typescript
// Start with Lighthouse for a comprehensive baseline score
const report = await mcp.call("lighthouse_audit", {
categories: ["accessibility"],
mode: "navigation", // refresh page + capture load issues
outputDirPath: "/tmp/lh-report",
});
// report.scores.accessibility → 0–1 scale (1.0 = no violations)
// report.audits.failed → list of failing checks with selectors
// Parse failures efficiently (don't read entire JSON):
// node -e "const r=require('/tmp/lh-report/report.json');
// Object.values(r.audits)
// .filter(a=>a.score!==null && a.score<1)
// .forEach(a=>console.log(JSON.stringify({
// id: a.id, title: a.title,
// items: a.details?.items?.slice(0,3)
// })))"
```
---
## Step 2: Browser-native issue detection
```typescript
// Chrome auto-checks for common a11y problems
const issues = await mcp.call("list_console_messages", {
types: ["issue"],
includePreservedMessages: true, // catch issues from page load
});
// Look for: missing labels, invalid ARIA, low-contrast text
// These are often critical failures missed by Lighthouse
```
---
## Step 3: Semantic structure audit
```typescript
// Capture accessibility tree
const snapshot = await mcp.call("take_snapshot", {
filePath: "/tmp/a11y-snapshot.json",
});
// Inspect heading levels programmatically
const headingAudit = await mcp.call("evaluate_script", {
script: `
const headings = [...document.querySelectorAll('h1,h2,h3,h4,h5,h6')]
.map(h => ({ level: parseInt(h.tagName[1]), text: h.textContent.trim().slice(0,60) }));
// Check for skipped levels
const issues = [];
for (let i = 1; i < headings.length; i++) {
if (headings[i].level > headings[i-1].level + 1)
issues.push({ skipped: true, from: headings[i-1], to: headings[i] });
}
return { headings, issues };
`
});
```
---
## Step 4: Labels, forms, and images
```typescript
// Find orphaned inputs (inputs without associated labels)
const orphanedInputs = await mcp.call("evaluate_script", {
script: `
[...document.querySelectorAll('input, textarea, select')]
.filter(el => {
const id = el.id;
const hasLabel = id && document.querySelector('label[for="' + id + '"]');
const hasAriaLabel = el.getAttribute('aria-label') || el.getAttribute('aria-labelledby');
return !hasLabel && !hasAriaLabel;
})
.map(el => ({ tag: el.tagName, type: el.type, id: el.id, name: el.name }))
`
});
// Check images for alt text
const imagesWithoutAlt = await mcp.call("evaluate_script", {
script: `
[...document.querySelectorAll('img')]
.filter(img => !img.alt && !img.getAttribute('aria-hidden'))
.map(img => ({ src: img.src.split('/').pop(), id: img.id }))
`
});
```
---
## Step 5: Focus and keyboard navigation
```typescript
// Test keyboard focus trap detection
// Navigate to the page, then use press_key to tab through
await mcp.call("navigate_page", { url: "https://example.com/dialog" });
// Open modal/dialog
await mcp.call("click", { uid: "open-dialog-uid" });
// Tab through 10 elements and capture focused element each time
const focusTrail = [];
for (let i = 0; i < 10; i++) {
await mcp.call("press_key", { key: "Tab" });
const focused = await mcp.call("evaluate_script", {
script: `JSON.stringify({
tag: document.activeElement.tagName,
role: document.activeElement.getAttribute('role'),
label: document.activeElement.getAttribute('aria-label') || document.activeElement.textContent?.trim().slice(0,30),
class: document.activeElement.className.slice(0,40),
})`
});
focusTrail.push(JSON.parse(focused));
}
// Check: focus should stay within dialog (trap); pressing Esc should close + return focus
```
---
## Step 6: Color contrast and tap targets
```typescript
// Color contrast audit (Lighthouse covers this, but for manual check:)
const contrastIssues = await mcp.call("evaluate_script", {
script: `
// Rough check: find elements with suspiciously similar fg/bg
[...document.querySelectorAll('p, span, a, button, label')]
.filter(el => {
const style = window.getComputedStyle(el);
const fg = style.color;
const bg = style.backgroundColor;
// Flag if both are near-grey or near-white (rough heuristic — use Lighthouse for accuracy)
return fg === bg || (fg === 'rgb(255,255,255)' && bg === 'rgba(0,0,0,0)');
})
.slice(0,5)
.map(el => ({ text: el.textContent.trim().slice(0,30), tag: el.tagName }))
`
});
// Tap target size (WCAG 2.5.5 — 44×44px minimum)
const smallTapTargets = await mcp.call("evaluate_script", {
script: `
[...document.querySelectorAll('a, button, [role="button"]')]
.filter(el => {
const rect = el.getBoundingClientRect();
return rect.width < 44 || rect.height < 44;
})
.slice(0, 10)
.map(el => ({ tag: el.tagName, text: el.textContent.trim().slice(0,30),
w: Math.round(el.getBoundingClientRect().width),
h: Math.round(el.getBoundingClientRect().height) }))
`
});
```
---
## Anti-Fake-Pass Checklist
```
❌ Using take_screenshot instead of take_snapshot for a11y audit → screenshot doesn't show ARIA roles
❌ Reading full Lighthouse JSON report → 500KB+ file; use jq/node filter for failures only
❌ opacity:0 treated as hidden from screen readers → still in accessibility tree; use aria-hidden
❌ Skipping browser-native issues check → Chrome catches critical errors Lighthouse misses
❌ Tab test without checking focus trap → focus escaping a dialog is a critical a11y failure
❌ Fixing "no alt text" on decorative images → add alt="" (empty) not alt="image" for decorative elements
```
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!