Babysitter docs-accessibility
Documentation accessibility validation and remediation. Check WCAG 2.1 compliance, validate alt text, analyze heading hierarchy, verify color contrast, and generate accessibility reports.
install
source · Clone the upstream repo
git clone https://github.com/a5c-ai/babysitter
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/a5c-ai/babysitter "$T" && mkdir -p ~/.claude/skills && cp -r "$T/library/specializations/technical-documentation/skills/docs-accessibility" ~/.claude/skills/a5c-ai-babysitter-docs-accessibility && rm -rf "$T"
manifest:
library/specializations/technical-documentation/skills/docs-accessibility/SKILL.mdtags
source content
Documentation Accessibility Skill
Documentation accessibility validation and remediation.
Capabilities
- WCAG 2.1 compliance checking
- Image alt text validation
- Heading hierarchy analysis
- Color contrast verification
- Screen reader compatibility testing
- Keyboard navigation validation
- ARIA landmark checking
- Accessibility report generation
Usage
Invoke this skill when you need to:
- Audit documentation for accessibility
- Validate image alt text
- Check heading structure
- Verify color contrast ratios
- Generate accessibility reports
Inputs
| 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 |
Input Example
{ "inputPath": "./docs/_build/html", "action": "audit", "standard": "AA", "outputFormat": "json" }
Output Structure
Accessibility Report
{ "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 } } }
WCAG Guidelines Checked
Perceivable (Principle 1)
1.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
Operable (Principle 2)
2.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
Understandable (Principle 3)
3.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
Image Alt Text Validation
Alt Text Rules
const 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=""' } };
Alt Text Suggestions
function 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; }
Heading Structure Analysis
Heading Hierarchy Check
function 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 }; }
Color Contrast Checking
Contrast Ratio Calculation
function 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; }
CSS Analysis
async 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; }
Keyboard Navigation
Focus Testing
async 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; }
Workflow
- Parse content - Load documentation files or built HTML
- Extract elements - Find images, headings, links, etc.
- Check images - Validate alt text
- Analyze headings - Check hierarchy
- Test contrast - Verify color ratios
- Check navigation - Validate keyboard access
- Generate report - Output findings
Dependencies
{ "devDependencies": { "axe-core": "^4.8.0", "pa11y": "^6.2.0", "lighthouse": "^11.0.0", "puppeteer": "^21.0.0", "color-contrast-checker": "^2.1.0" } }
CLI Commands
# 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
Best Practices Applied
- Always provide alt text for informative images
- Use empty alt for decorative images
- Maintain logical heading hierarchy
- Ensure 4.5:1 contrast for normal text
- Provide visible focus indicators
- Include skip navigation links
- Use semantic HTML elements
References
- WCAG 2.1: https://www.w3.org/WAI/WCAG21/quickref/
- axe-core: https://github.com/dequelabs/axe-core
- pa11y: https://pa11y.org/
- WebAIM Contrast Checker: https://webaim.org/resources/contrastchecker/
Target Processes
- docs-testing.js
- docs-audit.js
- style-guide-enforcement.js