Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Documentation accessibility validation and remediation. Check WCAG 2.1 compliance, validate alt text, analyze heading hierarchy, verify color contrast, and generate accessibility reports.
.claude/skills/a5c-ai-docs-accessibility/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | -2% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 2839% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 238% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 182% | 0% |
| case-22 | ✗→✓ | ▲ Improved | 184% | 0% |
Documentation accessibility validation and remediation.
Invoke this skill when you need to:
| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | inputPath | string | Yes | Path to documentation or built site | | action | string | Yes | audit, validate-images, check-headings | | standard | string | No | WCAG level (A, AA, AAA) | | outputFormat | string | No | json, html, sarif | | fix | boolean | No | Auto-fix issues where possible |
json{ "inputPath": "./docs/_build/html", "action": "audit", "standard": "AA", "outputFormat": "json" }
json{ "summary": { "total": 156, "passed": 142, "failed": 14, "level": "AA", "score": 91 }, "byCategory": { "images": { "passed": 45, "failed": 3 }, "headings": { "passed": 28, "failed": 2 }, "contrast": { "passed": 52, "failed": 5 }, "navigation": { "passed": 17, "failed": 4 } }, "issues": [ { "id": "img-alt-missing", "wcag": "1.1.1", "level": "A", "impact": "critical", "description": "Image missing alt text", "location": { "file": "docs/guide/setup.md", "line": 42, "element": "<img src=\"diagram.png\">" }, "suggestion": "Add descriptive alt text: alt=\"System architecture diagram showing...\"" }, { "id": "heading-skip", "wcag": "1.3.1", "level": "A", "impact": "moderate", "description": "Heading levels should only increase by one", "location": { "file": "docs/api/users.md", "line": 15, "element": "<h4>User Properties</h4>" }, "context": "H2 -> H4 (skipped H3)", "suggestion": "Change to <h3> or add missing <h3> above" }, { "id": "color-contrast", "wcag": "1.4.3", "level": "AA", "impact": "serious", "description": "Text does not meet contrast ratio requirements", "location": { "file": "docs/_static/custom.css", "line": 28, "element": ".note { color: #999; }" }, "details": { "foreground": "#999999", "background": "#ffffff", "ratio": "2.85:1", "required": "4.5:1" }, "suggestion": "Change to #767676 or darker for 4.5:1 ratio" } ], "wcagCompliance": { "A": { "passed": 48, "failed": 6 }, "AA": { "passed": 35, "failed": 8 }, "AAA": { "passed": 12, "failed": 0 } } }
yaml1.1.1 - Non-text Content: - Images have alt text - Decorative images have empty alt - Complex images have long descriptions - Icons have accessible names 1.3.1 - Info and Relationships: - Headings properly structured - Lists properly marked up - Tables have headers - Form labels associated 1.4.1 - Use of Color: - Color not sole indicator - Links distinguishable 1.4.3 - Contrast (Minimum): - Text: 4.5:1 ratio - Large text: 3:1 ratio - UI components: 3:1 ratio
yaml2.1.1 - Keyboard: - All functionality keyboard accessible - No keyboard traps - Skip links present 2.4.1 - Bypass Blocks: - Skip navigation link - Landmark regions 2.4.2 - Page Titled: - Descriptive page titles 2.4.6 - Headings and Labels: - Descriptive headings - Clear labels 2.4.7 - Focus Visible: - Visible focus indicators
yaml3.1.1 - Language of Page: - lang attribute present 3.2.3 - Consistent Navigation: - Navigation consistent across pages 3.3.2 - Labels or Instructions: - Form inputs have labels
javascriptconst altTextRules = { // Must have alt attribute required: { test: (img) => img.hasAttribute('alt'), message: 'Image must have alt attribute' }, // Alt text should be descriptive descriptive: { test: (img) => { const alt = img.getAttribute('alt'); const badPatterns = [ /^image$/i, /^photo$/i, /^picture$/i, /^graphic$/i, /\.(?:png|jpg|gif|svg)$/i, /^untitled/i ]; return !badPatterns.some(p => p.test(alt)); }, message: 'Alt text should describe the image content' }, // Not too long length: { test: (img) => { const alt = img.getAttribute('alt'); return alt.length <= 125; }, message: 'Alt text should be concise (under 125 characters)' }, // Decorative images should have empty alt decorative: { test: (img) => { if (img.hasAttribute('role') && img.getAttribute('role') === 'presentation') { return img.getAttribute('alt') === ''; } return true; }, message: 'Decorative images should have empty alt=""' } };
javascriptfunction suggestAltText(imagePath, context) { const suggestions = []; // Based on filename const filename = path.basename(imagePath, path.extname(imagePath)); if (filename.includes('diagram')) { suggestions.push(`Diagram showing ${extractContext(context)}`); } if (filename.includes('screenshot')) { suggestions.push(`Screenshot of ${extractContext(context)}`); } if (filename.includes('logo')) { suggestions.push(`${extractBrand(filename)} logo`); } // Based on surrounding text const heading = findNearestHeading(context); if (heading) { suggestions.push(`Illustration for ${heading}`); } return suggestions; }
javascriptfunction analyzeHeadings(content) { const headings = extractHeadings(content); const issues = []; let lastLevel = 0; headings.forEach((heading, index) => { const level = heading.level; // Check for skipped levels if (level > lastLevel + 1 && lastLevel !== 0) { issues.push({ type: 'heading-skip', heading: heading.text, line: heading.line, expected: lastLevel + 1, actual: level }); } // Check for multiple H1s if (level === 1 && index > 0) { issues.push({ type: 'multiple-h1', heading: heading.text, line: heading.line }); } lastLevel = level; }); return { structure: buildHeadingTree(headings), issues }; }
javascriptfunction getContrastRatio(foreground, background) { const fgLuminance = getRelativeLuminance(foreground); const bgLuminance = getRelativeLuminance(background); const lighter = Math.max(fgLuminance, bgLuminance); const darker = Math.min(fgLuminance, bgLuminance); return (lighter + 0.05) / (darker + 0.05); } function meetsContrastRequirement(ratio, isLargeText, level = 'AA') { const requirements = { 'AA': { normal: 4.5, large: 3 }, 'AAA': { normal: 7, large: 4.5 } }; const threshold = isLargeText ? requirements[level].large : requirements[level].normal; return ratio >= threshold; }
javascriptasync function analyzeStylesheet(cssPath) { const css = await fs.readFile(cssPath, 'utf8'); const ast = postcss.parse(css); const issues = []; ast.walkDecls('color', (decl) => { const rule = decl.parent; const bgColor = findBackgroundColor(rule) || '#ffffff'; const fgColor = decl.value; const ratio = getContrastRatio(fgColor, bgColor); if (!meetsContrastRequirement(ratio, false, 'AA')) { issues.push({ selector: rule.selector, foreground: fgColor, background: bgColor, ratio: ratio.toFixed(2), line: decl.source.start.line, suggestion: suggestAccessibleColor(fgColor, bgColor) }); } }); return issues; }
javascriptasync function testKeyboardNavigation(page) { const issues = []; // Get all focusable elements const focusable = await page.$$('a, button, input, select, textarea, [tabindex]'); for (const element of focusable) { await element.focus(); // Check focus visibility const hasFocusStyle = await page.evaluate((el) => { const styles = window.getComputedStyle(el); const focusStyles = window.getComputedStyle(el, ':focus'); return ( styles.outline !== 'none' || styles.boxShadow !== 'none' || focusStyles.outline !== 'none' ); }, element); if (!hasFocusStyle) { issues.push({ type: 'focus-not-visible', element: await element.evaluate(el => el.outerHTML.substring(0, 100)) }); } } return issues; }
json{ "devDependencies": { "axe-core": "^4.8.0", "pa11y": "^6.2.0", "lighthouse": "^11.0.0", "puppeteer": "^21.0.0", "color-contrast-checker": "^2.1.0" } }
bash# Run axe-core audit npx axe ./docs/_build/html --rules wcag2aa # Run pa11y npx pa11y https://docs.example.com --standard WCAG2AA # Lighthouse accessibility audit npx lighthouse https://docs.example.com --only-categories=accessibility # Check single page npx axe https://docs.example.com/guide --save report.json
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 11,546 | 25,087 | +117% | 1 | 1 | 0% | 342 | 5,942 | +1637% | 0 | 0 | — |
case-02 | fail→pass | 45,231 | 25,819 | -43% | 1 | 1 | 0% | 6,712 | 6,550 | -2% | 0 | 0 | — |
case-03 | fail→pass | 17,239 | 49,629 | +188% | 1 | 1 | 0% | 389 | 11,431 | +2839% | 0 | 0 | — |
case-04 | fail→fail | 15,804 | 16,466 | +4% | 1 | 1 | 0% | 1,917 | 5,158 | +169% | 0 | 0 | — |
case-05 | fail→fail | 10,519 | 12,837 | +22% | 1 | 1 | 0% | 655 | 4,530 | +592% | 0 | 0 | — |
case-06 | fail→fail | 10,800 | 22,975 | +113% | 1 | 1 | 0% | 2,007 | 5,983 | +198% | 0 | 0 | — |
case-07 | pass→pass | 8,672 | 8,117 | -6% | 1 | 1 | 0% | 1,314 | 4,387 | +234% | 0 | 0 | — |
case-08 | pass→pass | 3,915 | 8,820 | +125% | 1 | 1 | 0% | 509 | 3,708 | +628% | 0 | 0 | — |
case-09 | pass→pass | 4,504 | 10,668 | +137% | 1 | 1 | 0% | 651 | 3,965 | +509% | 0 | 0 | — |
case-10 | pass→pass | 10,273 | 8,078 | -21% | 1 | 1 | 0% | 838 | 3,688 | +340% | 0 | 0 | — |
case-11 | pass→pass | 18,113 | 12,882 | -29% | 1 | 1 | 0% | 2,043 | 4,599 | +125% | 0 | 0 | — |
case-12 | pass→pass | 11,822 | 11,458 | -3% | 1 | 1 | 0% | 1,089 | 4,077 | +274% | 0 | 0 | — |
case-13 | pass→pass | 14,968 | 3,446 | -77% | 1 | 1 | 0% | 1,390 | 3,798 | +173% | 0 | 0 | — |
case-14 | pass→pass | 8,499 | 8,003 | -6% | 1 | 1 | 0% | 1,114 | 3,638 | +227% | 0 | 0 | — |
case-15 | fail→pass | 10,891 | 2,755 | -75% | 1 | 1 | 0% | 1,091 | 3,690 | +238% | 0 | 0 | — |
case-16 | fail→pass | 7,459 | 4,767 | -36% | 1 | 1 | 0% | 1,419 | 4,001 | +182% | 0 | 0 | — |
case-17 | pass→pass | 10,982 | 4,604 | -58% | 1 | 1 | 0% | 978 | 3,797 | +288% | 0 | 0 | — |
case-18 | pass→pass | 4,901 | 2,733 | -44% | 1 | 1 | 0% | 820 | 3,626 | +342% | 0 | 0 | — |
case-19 | pass→pass | 8,507 | 2,724 | -68% | 1 | 1 | 0% | 598 | 3,625 | +506% | 0 | 0 | — |
case-20 | pass→pass | 15,644 | 20,393 | +30% | 1 | 1 | 0% | 2,102 | 5,638 | +168% | 0 | 0 | — |
case-21 | pass→pass | 20,261 | 18,696 | -8% | 1 | 1 | 0% | 2,535 | 5,541 | +119% | 0 | 0 | — |
case-22 | fail→pass | 7,749 | 7,546 | -3% | 1 | 1 | 0% | 1,284 | 3,644 | +184% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 20 counted toward the lift figure. The other 2 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +23 percentage points is the difference between those two pass rates over the 20 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.