| name | docs-accessibility |
| description | Documentation accessibility validation and remediation. Check WCAG 2.1 compliance, validate alt text, analyze heading hierarchy, verify color contrast, and generate accessibility reports. |
| allowed-tools | Read, Write, Edit, Bash, Glob, Grep |
| backlog-id | SK-019 |
| metadata | {"author":"babysitter-sdk","version":"1.0.0"} |
| graph | {"domains":["domain:software-engineering"],"specializations":["specialization:technical-documentation"],"skillAreas":["skill-area:accessibility-testing","skill-area:docs-as-code"],"roles":["role:technical-writer","role:documentation-engineer"],"topics":["topic:accessibility"]} |
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"
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:
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 = {
required: {
test: (img) => img.hasAttribute('alt'),
message: 'Image must have alt attribute'
},
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'
},
length: {
test: (img) => {
const alt = img.getAttribute('alt');
return alt.length <= 125;
},
message: 'Alt text should be concise (under 125 characters)'
},
decorative: {
test: () => {
(img.() && img.() === ) {
img.() === ;
}
;
},
:
}
};
Alt Text Suggestions
function suggestAltText(imagePath, context) {
const suggestions = [];
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`);
}
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;
if (level > lastLevel + 1 && lastLevel !== 0) {
issues.push({
type: 'heading-skip',
heading: heading.text,
line: heading.line,
expected: lastLevel + 1,
actual: level
});
}
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 = [];
const focusable = await page.$$('a, button, input, select, textarea, [tabindex]');
for (const element of focusable) {
await element.focus();
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
npx axe ./docs/_build/html --rules wcag2aa
npx pa11y https://docs.example.com --standard WCAG2AA
npx lighthouse https://docs.example.com --only-categories=accessibility
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
Target Processes
- docs-testing.js
- docs-audit.js
- style-guide-enforcement.js