Conduct WCAG 2.2 accessibility audits with automated testing, manual verification, and remediation guidance. Use when auditing websites for accessibility, fixing WCAG violations, or implementing accessible design patterns.
Inherits all available tools
Additional assets for this skill
This skill inherits all available tools. When active, it can use any tool Claude has access to.
Comprehensive guide to auditing web content against WCAG 2.2 guidelines with actionable remediation strategies.
| Level | Description | Required For |
|---|---|---|
| A | Minimum accessibility | Legal baseline |
| AA | Standard conformance | Most regulations |
| AAA | Enhanced accessibility | Specialized needs |
Perceivable: Can users perceive the content?
Operable: Can users operate the interface?
Understandable: Can users understand the content?
Robust: Does it work with assistive tech?
Critical (Blockers):
├── Missing alt text for functional images
├── No keyboard access to interactive elements
├── Missing form labels
└── Auto-playing media without controls
Serious:
├── Insufficient color contrast
├── Missing skip links
├── Inaccessible custom widgets
└── Missing page titles
Moderate:
├── Missing language attribute
├── Unclear link text
├── Missing landmarks
└── Improper heading hierarchy
## 1.1 Text Alternatives
### 1.1.1 Non-text Content (Level A)
- [ ] All images have alt text
- [ ] Decorative images have alt=""
- [ ] Complex images have long descriptions
- [ ] Icons with meaning have accessible names
- [ ] CAPTCHAs have alternatives
Check:
```html
<!-- Good -->
<img src="chart.png" alt="Sales increased 25% from Q1 to Q2">
<img src="decorative-line.png" alt="">
<!-- Bad -->
<img src="chart.png">
<img src="decorative-line.png" alt="decorative line">
Check:
<!-- Heading hierarchy -->
<h1>Page Title</h1>
<h2>Section</h2>
<h3>Subsection</h3>
<h2>Another Section</h2>
<!-- Table headers -->
<table>
<thead>
<tr><th scope="col">Name</th><th scope="col">Price</th></tr>
</thead>
</table>
Tools: WebAIM Contrast Checker, axe DevTools
### Operable (Principle 2)
```markdown
## 2.1 Keyboard Accessible
### 2.1.1 Keyboard (Level A)
- [ ] All functionality keyboard accessible
- [ ] No keyboard traps
- [ ] Tab order is logical
- [ ] Custom widgets are keyboard operable
Check:
```javascript
// Custom button must be keyboard accessible
<div role="button" tabindex="0"
onkeydown="if(event.key === 'Enter' || event.key === ' ') activate()">
@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
transition: none !important;
}
}
<a href="#main" class="skip-link">Skip to main content</a>
<main id="main">...</main>
<!-- Bad -->
<a href="report.pdf">Click here</a>
<!-- Good -->
<a href="report.pdf">Download Q4 Sales Report (PDF)</a>
:focus {
outline: 3px solid #005fcc;
outline-offset: 2px;
}
### Understandable (Principle 3)
```markdown
## 3.1 Readable
### 3.1.1 Language of Page (Level A)
- [ ] HTML lang attribute set
- [ ] Language correct for content
```html
<html lang="en">
<p>The French word <span lang="fr">bonjour</span> means hello.</p>
<input aria-describedby="email-error" aria-invalid="true">
<span id="email-error" role="alert">Please enter valid email</span>
### Robust (Principle 4)
```markdown
## 4.1 Compatible
### 4.1.1 Parsing (Level A) - Obsolete in WCAG 2.2
- [ ] Valid HTML (good practice)
- [ ] No duplicate IDs
- [ ] Complete start/end tags
### 4.1.2 Name, Role, Value (Level A)
- [ ] Custom widgets have accessible names
- [ ] ARIA roles correct
- [ ] State changes announced
```html
<!-- Accessible custom checkbox -->
<div role="checkbox"
aria-checked="false"
tabindex="0"
aria-labelledby="label">
</div>
<span id="label">Accept terms</span>
<div role="status" aria-live="polite">
3 items added to cart
</div>
<div role="alert" aria-live="assertive">
Error: Form submission failed
</div>
## Automated Testing
```javascript
// axe-core integration
const axe = require('axe-core');
async function runAccessibilityAudit(page) {
await page.addScriptTag({ path: require.resolve('axe-core') });
const results = await page.evaluate(async () => {
return await axe.run(document, {
runOnly: {
type: 'tag',
values: ['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa']
}
});
});
return {
violations: results.violations,
passes: results.passes,
incomplete: results.incomplete
};
}
// Playwright test example
test('should have no accessibility violations', async ({ page }) => {
await page.goto('/');
const results = await runAccessibilityAudit(page);
expect(results.violations).toHaveLength(0);
});
# CLI tools
npx @axe-core/cli https://example.com
npx pa11y https://example.com
lighthouse https://example.com --only-categories=accessibility
<!-- Before -->
<input type="email" placeholder="Email">
<!-- After: Option 1 - Visible label -->
<label for="email">Email address</label>
<input id="email" type="email">
<!-- After: Option 2 - aria-label -->
<input type="email" aria-label="Email address">
<!-- After: Option 3 - aria-labelledby -->
<span id="email-label">Email</span>
<input type="email" aria-labelledby="email-label">
/* Before: 2.5:1 contrast */
.text { color: #767676; }
/* After: 4.5:1 contrast */
.text { color: #595959; }
/* Or add background */
.text {
color: #767676;
background: #000;
}
// Make custom element keyboard accessible
class AccessibleDropdown extends HTMLElement {
connectedCallback() {
this.setAttribute('tabindex', '0');
this.setAttribute('role', 'combobox');
this.setAttribute('aria-expanded', 'false');
this.addEventListener('keydown', (e) => {
switch (e.key) {
case 'Enter':
case ' ':
this.toggle();
e.preventDefault();
break;
case 'Escape':
this.close();
break;
case 'ArrowDown':
this.focusNext();
e.preventDefault();
break;
case 'ArrowUp':
this.focusPrevious();
e.preventDefault();
break;
}
});
}
}