Use when running comprehensive WCAG accessibility audits with axe-core + pa11y + Lighthouse, generating context-aware remediation, or testing video accessibility. Supports 3-tier browser cascade with graceful degradation.
Use when running comprehensive WCAG accessibility audits with axe-core + pa11y + Lighthouse, generating context-aware remediation, or testing video accessibility. Supports 3-tier browser cascade with graceful degradation.
<default_to_action>
When this skill is invoked with a URL, Claude executes ALL steps automatically without waiting for user prompts between steps.
THIS IS AN LLM-POWERED SKILL
The value of this skill is Claude's intelligence, not just running automated tools:
Automated Tools Do
Claude (This Skill) Does
Flag "button has no name"
Analyze context: icon class, parent element, nearby text → generate "Add to wishlist"
Flag "image missing alt"
Use Vision to see the image → describe actual content
Flag "video has no captions"
Download video, extract frames, analyze each frame with Vision → generate real captions
Output generic templates
Generate context-specific, copy-paste ready fixes
IF YOU SKIP THE LLM ANALYSIS, THIS SKILL HAS NO VALUE.
EXECUTION MODEL
CLAUDE EXECUTES ALL STEPS WITHOUT STOPPING.
Do NOT wait for user prompts between steps. Execute the full pipeline:
Data Collection: Run multi-tool scan (axe-core, pa11y, Lighthouse) via Bash
LLM Analysis: Read results and analyze context for each violation
Vision Pipeline: If videos detected → download → extract frames → Read each frame → describe
Intelligent Remediation: Generate context-specific fixes using your reasoning
Generate Reports: Write all output files to docs/accessibility-scans/{page-slug}/
WRONG:
Claude: "I found 5 violations. Should I analyze them?"
User: "Yes"
Claude: "I see a video. Should I run the video pipeline?"
User: "Yes"
RIGHT:
Claude: [Runs scan] → [Analyzes violations] → [Downloads video] → [Extracts frames] →
[Reads each frame with Vision] → [Generates captions] → [Writes all files]
"Audit complete. Generated 4 files in docs/accessibility-scans/example/"
STEP 1: BROWSER AUTOMATION - Content Fetching
Uses the qe-browser fleet skill as the browser engine. qe-browser wraps Vibium (WebDriver BiDi, 10MB Go binary) and provides the QE primitives we rely on. See .claude/skills/qe-browser/SKILL.md.
1.1: PRIMARY — qe-browser via Vibium CLI
# Navigate
vibium go "$TARGET_URL"
vibium wait load
# Capture accessibility tree without visual render
vibium a11y-tree --json > /tmp/a11y-work/tree.json
# Screenshot for Vision pipeline
vibium screenshot -o /tmp/a11y-work/page.png --full-page
If Vibium MCP tools are registered (mcp__vibium__*), prefer them; otherwise shell out to the vibium binary installed by aqe init.
1.2: Run axe-core + WCAG assertions via qe-browser
# Only use when you need the extra rulesets, not as the primary path
pa11y "$TARGET_URL" --reporter json > /tmp/a11y-work/pa11y.json
lighthouse "$TARGET_URL" --only-categories=accessibility --output=json --output-path=/tmp/a11y-work/lighthouse.json --chrome-flags="--headless"
Why we dropped playwright-extra + puppeteer-extra-plugin-stealth from the primary path:
300MB+ of Node deps vs Vibium's 10MB binary
Redundant: Vibium uses WebDriver BiDi which is less fingerprintable than raw CDP
Simpler: one tool instead of a cascade
1d: PARALLEL MULTI-PAGE AUDIT (Optional)
For auditing multiple URLs simultaneously, use parallel execution:
violations: All violations from all tools (deduplicated)
byTool: Success/failure status per tool
toolsSucceeded: Number of tools that completed (1-3)
2.3: Graceful Degradation Decision Tree
Tools Succeeded
Action
3/3
✅ Full coverage - proceed with all results
2/3
⚠️ Good coverage - note which tool failed in report
1/3
⚠️ Basic coverage - proceed but flag limited confidence
0/3
❌ Retry with Vibium MCP, or document failure
2.4: MANDATORY - Check for Videos and Trigger Pipeline
After reading scan results, check pageInfo.media.videoUrls:
// Check scan-results.json for videosconst results = JSON.parse(fs.readFileSync('/tmp/a11y-work/scan-results.json'));
if (results.pageInfo && results.pageInfo.media.videoUrls.length > 0) {
console.log('=== VIDEOS DETECTED - TRIGGERING VIDEO PIPELINE ===');
for (const video of results.pageInfo.media.videoUrls) {
console.log(`Video: ${video.src}`);
console.log(` Has captions: ${video.hasCaptions}`);
}
// PROCEED TO STEP 7 IMMEDIATELY
}
IF videos detected AND hasCaptions=false → STEP 7 is MANDATORY before generating reports.
STEP 3: CONTEXT-AWARE REMEDIATION (LLM-POWERED)
THIS IS WHERE CLAUDE'S INTELLIGENCE MATTERS.
Generic tools output: aria-label="[DESCRIPTION]"
You output: aria-label="Add to shopping cart" because you understand context.
3.1: Context Analysis (Use Your Reasoning)
For EACH violation, Claude must:
READ THE HTML CONTEXT - Don't just see <button class="btn">, see:
<divclass="product-card"data-product="Adidas Superstar"><imgsrc="superstar.jpg"alt="White sneakers"><spanclass="price">$99</span><buttonclass="btn add-to-cart"><!-- THIS IS THE VIOLATION --><svgclass="icon-cart">...</svg></button></div>
INFER PURPOSE from:
Class names: add-to-cart, wishlist, menu-toggle
Parent context: Inside .product-card with product data
Icon classes: icon-cart, icon-heart, icon-search
Nearby text: Product name, price, "Add to bag"
Page section: Header nav vs product grid vs checkout
GENERATE SPECIFIC FIX:
<!-- NOT THIS (generic template) --><buttonaria-label="[DESCRIPTION]"><!-- THIS (context-aware) --><buttonaria-label="Add Adidas Superstar to cart - $99">
3.2: Confidence Scoring
Rate your confidence in each fix:
0.9+: Clear context (class="add-to-cart" near product name)
0.7-0.9: Reasonable inference (icon-cart class alone)
<0.7: Needs human review (ambiguous context)
Include confidence in remediation.md:
### Button: `.product-card .btn` (Confidence: 0.95)**Context:** Inside product card for "Adidas Superstar", has cart icon
**Fix:**`aria-label="Add Adidas Superstar to cart"`
3.2: Remediation Templates by Violation Type
Form Labels (WCAG 1.3.1, 3.3.2, 4.1.2)
<!-- Context: Input inside payment form, near "Card Number" text --><!-- Confidence: 0.95 --><!-- BEFORE --><inputtype="text"name="cardNumber"placeholder="1234 5678 9012 3456"><!-- AFTER --><labelfor="card-number">Credit Card Number</label><inputtype="text"id="card-number"name="cardNumber"placeholder="1234 5678 9012 3456"aria-describedby="card-hint"autocomplete="cc-number"inputmode="numeric"pattern="[0-9\s]{13,19}"><spanid="card-hint"class="visually-hidden">Enter 16-digit card number</span><!-- RATIONALE -->
- Visible label aids all users
- aria-describedby provides additional context
- autocomplete enables autofill
- inputmode shows numeric keyboard on mobile
- pattern enables browser validation
Icon Buttons (WCAG 4.1.2)
<!-- Context: Button with SVG inside nav, classes include "menu-toggle" --><!-- Confidence: 0.92 --><!-- BEFORE --><buttonclass="menu-toggle"><svg>...</svg></button><!-- AFTER --><buttonclass="menu-toggle"type="button"aria-expanded="false"aria-controls="main-menu"aria-label="Open navigation menu"><svgaria-hidden="true"focusable="false">...</svg></button><!-- RATIONALE -->
- aria-label describes action, not icon
- aria-expanded communicates state
- aria-controls links to menu element
- SVG hidden from assistive tech (decorative)
Color Contrast (WCAG 1.4.3)
<!-- Context: Gray text (#767676) on white background --><!-- Current ratio: 4.48:1 (FAILS AA for normal text) --><!-- Required: 4.5:1 (AA) or 7:1 (AAA) --><!-- BEFORE -->
.low-contrast { color: #767676; background: #ffffff; }
<!-- AFTER (Option 1: Darken text - minimal change) -->
.accessible { color: #757575; background: #ffffff; } /* 4.6:1 - PASSES AA */
<!-- AFTER (Option 2: Higher contrast for AAA) -->
.high-contrast { color: #595959; background: #ffffff; } /* 7.0:1 - PASSES AAA */
<!-- COLOR ALTERNATIVES -->
| Original | AA Pass | AAA Pass | Notes |
|----------|---------|----------|-------|
| #767676 | #757575 | #595959 | Gray text |
| #0066cc | #0055b3 | #003d82 | Link blue |
| #cc0000 | #b30000 | #8b0000 | Error red |
Heading Hierarchy (WCAG 1.3.1)
<!-- Context: Page has 10 H1 elements, skipped H2 levels --><!-- BEFORE (broken) --><h1>Welcome</h1><h1>Products</h1><!-- ERROR: Multiple H1s --><h4>Shoes</h4><!-- ERROR: Skipped H2, H3 --><h1>Contact</h1><!-- AFTER (correct) --><h1>Site Name - Main Page Title</h1><main><sectionaria-labelledby="products-heading"><h2id="products-heading">Products</h2><h3>Shoes</h3><h3>Clothing</h3></section><sectionaria-labelledby="contact-heading"><h2id="contact-heading">Contact</h2></section></main><!-- HEADING STRUCTURE VISUALIZATION -->
h1: Site Name - Main Page Title
├── h2: Products
│ ├── h3: Shoes
│ └── h3: Clothing
└── h2: Contact
Skip Links (WCAG 2.4.1)
<!-- Add as FIRST element inside <body> --><body><ahref="#main-content"class="skip-link">Skip to main content</a><ahref="#main-nav"class="skip-link">Skip to navigation</a><header><navid="main-nav"aria-label="Main navigation">...</nav></header><mainid="main-content"tabindex="-1"><!-- Main content --></main></body><style>.skip-link {
position: absolute;
top: -100%;
left: 16px;
background: #000;
color: #fff;
padding: 12px24px;
z-index: 10000;
text-decoration: none;
font-weight: bold;
border-radius: 004px4px;
transition: top 0.2s;
}
.skip-link:focus {
top: 0;
outline: 3px solid #ffcc00;
outline-offset: 2px;
}
</style>
Focus Indicators (WCAG 2.4.7)
/* NEVER do this */
*:focus { outline: none; } /* WCAG FAIL *//* DO THIS - Custom focus styles */:focus-visible {
outline: 3px solid #005fcc;
outline-offset: 2px;
}
/* Remove outline only for mouse users */:focus:not(:focus-visible) {
outline: none;
}
/* High contrast for interactive elements */a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[role="button"]:focus-visible {
outline: 3px solid #005fcc;
outline-offset: 2px;
box-shadow: 0006pxrgba(0, 95, 204, 0.2);
}
/* Dark backgrounds need light focus */.dark-bg:focus-visible {
outline-color: #ffffff;
box-shadow: 0006pxrgba(255, 255, 255, 0.3);
}
Keyboard Navigation (WCAG 2.1.1, 2.1.2)
<!-- Custom interactive element needs keyboard support --><!-- BEFORE (inaccessible) --><divclass="dropdown"onclick="toggleMenu()">
Menu
</div><!-- AFTER (accessible) --><buttontype="button"class="dropdown-trigger"aria-expanded="false"aria-controls="dropdown-menu"onclick="toggleMenu()"onkeydown="handleKeydown(event)">
Menu
</button><ulid="dropdown-menu"role="menu"hidden><lirole="none"><arole="menuitem"href="/page1">Page 1</a></li><lirole="none"><arole="menuitem"href="/page2">Page 2</a></li></ul><script>functionhandleKeydown(event) {
switch(event.key) {
case'Enter':
case' ':
event.preventDefault();
toggleMenu();
break;
case'Escape':
closeMenu();
break;
case'ArrowDown':
event.preventDefault();
focusFirstMenuItem();
break;
}
}
</script>
Modal Focus Trap (WCAG 2.4.3)
// Focus trap for modals - REQUIRED for WCAG compliancefunctiontrapFocus(modal) {
const focusable = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
// Focus first element when modal opens
first?.focus();
modal.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} elseif (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
if (e.key === 'Escape') {
closeModal();
}
});
}
// Return focus when modal closesfunctioncloseModal() {
modal.hidden = true;
triggerButton.focus(); // Return focus to trigger
}
iframe Titles (WCAG 4.1.2)
<!-- All iframes MUST have descriptive titles --><iframesrc="map.html"title="Store location map showing 5 nearby stores"></iframe><iframesrc="video.html"title="Product demonstration video with captions"></iframe><iframesrc="chat.html"title="Customer support chat window"></iframe>
STEP 4: USER IMPACT ANALYSIS
For each violation, calculate user impact:
4.1: Affected User Groups
Violation Type
Affected Groups
% of Users
Missing alt text
Blind, low-vision
7-10%
Missing form labels
Blind, screen reader users
5-8%
Low color contrast
Low-vision, color blind
8-12%
No keyboard access
Motor impaired, power users
10-15%
Missing captions
Deaf, hard-of-hearing
5-7%
Flashing content
Seizure sensitive
0.5-1%
Complex language
Cognitive impairment
10-15%
4.2: Impact Severity Classification
BLOCKS-USAGE: User cannot complete task at all
- Missing form labels on required fields
- Keyboard traps
- Critical buttons without accessible names
IMPAIRS-USAGE: User can complete task with difficulty
- Low contrast (can read with effort)
- Missing skip links (tedious navigation)
- Incorrect heading structure (confusing)
MINOR-INCONVENIENCE: Suboptimal but functional
- Empty alt on decorative images
- Redundant ARIA
- Non-semantic HTML that works
STEP 5: ROI-BASED PRIORITIZATION
Calculate priority for each remediation:
5.1: Priority Formula
PRIORITY_SCORE = (IMPACT_WEIGHT × USERS_AFFECTED) / EFFORT_HOURS
Where:
- IMPACT_WEIGHT: Critical=10, Serious=7, Moderate=4, Minor=1
- USERS_AFFECTED: Estimated % of users impacted
- EFFORT_HOURS: Estimated fix time (0.25 to 8 hours)
Frame 1 (0:00-0:03): A woman in white Adidas sneakers running on a forest trail.
Morning light filters through trees. She wears black athletic leggings and a
gray tank top. The Adidas three-stripe logo is visible on her shoes.
THIS IS THE LLM VALUE. Generic tools output "[DESCRIBE CONTENT]".
You output actual descriptions because you can SEE the image.
Manual testing instructions (cannot be fully automated):
10.1: NVDA (Windows - Free)
1. Download: https://www.nvaccess.org/download/
2. Install and start NVDA (Ctrl+Alt+N)
3. Navigate to audited page
Key Commands:
- H: Jump through headings
- F: Jump through form fields
- B: Jump through buttons
- T: Jump through tables
- K: Jump through links
- D: Jump through landmarks
- Tab: Move through focusable elements
Verify:
- [ ] All headings announced with correct level
- [ ] Form fields announce labels
- [ ] Buttons announce purpose
- [ ] Images announce alt text or "decorative"
- [ ] Dynamic content changes announced (aria-live)
10.2: VoiceOver (macOS - Built-in)
1. Enable: System Preferences → Accessibility → VoiceOver
2. Toggle: Cmd+F5
3. Navigate to audited page
Key Commands:
- VO+U: Open rotor (headings, links, forms, landmarks)
- VO+Space: Activate element
- VO+Right/Left: Move through content
- VO+Cmd+H: Jump to next heading
Verify:
- [ ] Rotor shows all headings hierarchically
- [ ] Forms are navigable and labels announced
- [ ] Focus order matches visual order
- [ ] All content is reachable
10.3: JAWS (Windows - Commercial)
1. Trial: https://www.freedomscientific.com/products/software/jaws/
2. Start JAWS and navigate to page
Key Commands:
- H: Next heading
- F: Next form field
- B: Next button
- T: Next table
- Ins+F6: Heading list
- Ins+F7: Link list
Verify:
- [ ] Virtual cursor mode works correctly
- [ ] Forms mode activates in forms
- [ ] All ARIA roles announced properly