a11y
Production-grade accessibility skill for WCAG 2.2 AA compliance.Covers auditing, remediation, component authoring, and validation workflows.Auto-invoked for UI implementation, a11y fixes, and accessibility testing.
When & Why to Use This Skill
This Claude skill provides a production-grade framework for achieving WCAG 2.2 Level AA accessibility compliance. It streamlines the entire a11y lifecycle—from automated auditing and code remediation to semantic component authoring and validation workflows—ensuring web applications meet the highest standards of inclusive design and legal requirements.
Use Cases
- Automated Accessibility Auditing: Scan existing React/TSX codebases to identify and fix WCAG 2.2 violations such as missing ARIA labels, incorrect heading hierarchies, or non-semantic HTML.
- Accessible UI Component Authoring: Generate complex, WAI-ARIA 1.2 compliant interface patterns like TreeViews, Modal Dialogs, and Toasts with built-in keyboard navigation and roving tabindex.
- CI/CD Validation Workflows: Integrate automated testing tools like vitest-axe, Lighthouse, and pa11y into development pipelines to prevent accessibility regressions in production.
- Visual and Motion Compliance: Implement high-contrast color schemes and 'prefers-reduced-motion' CSS configurations to support users with visual impairments or vestibular sensitivities.
- Manual Testing Guidance: Provide structured checklists and screen reader testing protocols (NVDA/VoiceOver) to verify the real-world usability of interactive web elements.
| name | a11y |
|---|---|
| description | | |
Accessibility implementation guide aligned with WCAG 2.2 Level AA, WAI-ARIA 1.2, and WCAG2ICT.
Standards Reference
| Standard | Scope | Normative Source |
|---|---|---|
| WCAG 2.2 | Web content | https://www.w3.org/TR/WCAG22/ |
| WAI-ARIA 1.2 | Widget semantics | https://www.w3.org/TR/wai-aria-1.2/ |
| ARIA APG | Authoring patterns | https://www.w3.org/WAI/ARIA/apg/ |
| WCAG2ICT | Non-web ICT | https://www.w3.org/TR/wcag2ict-22/ |
| EN 301 549 | EU procurement | ETSI EN 301 549 V3.2.1 |
WCAG 2.2 New Success Criteria
| SC | Level | Requirement | Implementation |
|---|---|---|---|
| 2.4.11 | AA | Focus Not Obscured (Minimum) | Ensure focused element is at least partially visible |
| 2.4.13 | AAA | Focus Appearance | Focus indicator area ≥ 2px perimeter, 3:1 contrast |
| 2.5.7 | A | Dragging Movements | Provide single-pointer alternative to drag operations |
| 2.5.8 | AA | Target Size (Minimum) | 24×24 CSS pixels minimum |
| 3.2.6 | A | Consistent Help | Place help mechanisms in same relative location |
| 3.3.7 | A | Redundant Entry | Auto-populate previously entered information |
| 3.3.8 | AA | Accessible Authentication | No cognitive function test for login |
Critical Success Criteria
Perceivable
| SC | Requirement | Implementation | Test |
|---|---|---|---|
| 1.1.1 | Non-text content | alt on images, aria-label on icon buttons |
img[alt], button[aria-label] |
| 1.3.1 | Info and relationships | Semantic HTML, no <div> for structure |
Landmark audit |
| 1.4.3 | Contrast (minimum) | 4.5:1 text, 3:1 large text | axe color-contrast |
| 1.4.11 | Non-text contrast | 3:1 UI components/graphics | Manual inspection |
Operable
| SC | Requirement | Implementation | Test |
|---|---|---|---|
| 2.1.1 | Keyboard | All functions keyboard-accessible | Tab traversal |
| 2.4.3 | Focus order | Logical DOM sequence | Visual focus path |
| 2.4.7 | Focus visible | 2px+ visible indicator | focus-visible:ring-2 |
| 2.5.8 | Target size | Minimum 24x24 CSS pixels | min-w-6 min-h-6 |
Understandable
| SC | Requirement | Implementation | Test |
|---|---|---|---|
| 3.2.1 | On focus | No context change on focus | Focus does not submit |
| 3.3.1 | Error identification | Text description, not color alone | role="alert" present |
Robust
| SC | Requirement | Implementation | Test |
|---|---|---|---|
| 4.1.2 | Name, role, value | Accessible name on all controls | axe button-name |
Semantic HTML (Mandatory)
// REQUIRED: Landmark structure
<header role="banner">...</header>
<nav aria-label="Main">...</nav>
<main role="main">...</main>
<aside role="complementary">...</aside>
<footer role="contentinfo">...</footer>
// PROHIBITED: Div-based structure
<div class="header"> // SC 1.3.1 violation
<div onClick={...}> // SC 2.1.1, 4.1.2 violation
Top 10 Violations with Fixes
1. Missing button name (SC 4.1.2)
// BAD
<button onClick={handleDelete}><Trash2 /></button>
// GOOD
<button type="button" aria-label="Delete" onClick={handleDelete}>
<Trash2 aria-hidden="true" />
</button>
2. Div as interactive element (SC 2.1.1, 4.1.2)
// BAD
<div className="btn" onClick={handleClick}>Save</div>
// GOOD
<button type="button" onClick={handleClick}>Save</button>
3. Missing form label (SC 1.3.1, 4.1.2)
// BAD
<input placeholder="Search..." />
// GOOD
<label htmlFor="search">Search</label>
<input id="search" type="search" />
// OR
<input type="search" aria-label="Search files" />
4. Color-only information (SC 1.4.1)
// BAD
<input className={error ? "border-red-500" : ""} />
// GOOD
<input aria-invalid={!!error} aria-describedby="error-msg" />
{error && <span id="error-msg" role="alert">{error}</span>}
5. Missing alt text (SC 1.1.1)
// BAD
<img src="/logo.png" />
// GOOD: Informative
<img src="/logo.png" alt="Company logo" />
// GOOD: Decorative
<img src="/decoration.png" alt="" role="presentation" />
6. Insufficient contrast (SC 1.4.3)
/* BAD: ~2.5:1 ratio */
.muted { color: oklch(75% 0 0); }
/* GOOD: 4.5:1+ ratio */
.muted { color: oklch(45% 0 0); }
Color Vision Deficiency (CVD) Support
Never rely on color alone to convey information (SC 1.4.1):
// BAD: Color-only status
<span className={status === "error" ? "text-red-500" : "text-green-500"}>
{status}
</span>
// GOOD: Color + icon + text
<span className={status === "error" ? "text-red-500" : "text-green-500"}>
{status === "error" ? <AlertCircle aria-hidden /> : <CheckCircle aria-hidden />}
{status === "error" ? "Error" : "Success"}
</span>
Testing tools:
- Chrome DevTools: Rendering → Emulate vision deficiencies
- Sim Daltonism (macOS)
- Color Oracle (Windows)
7. Missing focus indicator (SC 2.4.7)
// BAD
<button className="outline-none">Action</button>
// GOOD
<button className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
Action
</button>
8. Skipped heading levels (SC 1.3.1)
// BAD
<h1>Title</h1>
<h3>Subsection</h3> // h2 skipped
// GOOD
<h1>Title</h1>
<h2>Subsection</h2>
9. Small touch target (SC 2.5.8)
// BAD: 16x16
<button className="p-0.5"><X size={12} /></button>
// GOOD: 24x24 minimum
<button className="p-2 min-w-6 min-h-6"><X size={16} /></button>
10. Auto-playing media (SC 1.4.2)
// BAD
<video src="/demo.mp4" autoPlay />
// GOOD
<video src="/demo.mp4" autoPlay muted />
Component Patterns
TreeView (APG)
<ul role="tree" aria-label="File explorer">
<li
role="treeitem"
aria-expanded={isExpanded}
aria-selected={isSelected}
aria-level={level}
aria-setsize={siblingCount}
aria-posinset={position}
tabIndex={isFocused ? 0 : -1} // Roving tabindex
>
<span>{name}</span>
{hasChildren && (
<ul role="group">{children}</ul>
)}
</li>
</ul>
Keyboard: ↓↑ navigate, → expand/child, ← collapse/parent, Enter activate, Space toggle, Home/End boundaries
Modal Dialog (APG)
<div
role="dialog"
aria-modal="true"
aria-labelledby="dialog-title"
tabIndex={-1}
>
<h2 id="dialog-title">Title</h2>
{/* Focus trap: Tab cycles within */}
{/* Escape: closes dialog */}
{/* On close: restore focus to trigger */}
</div>
Toast/Alert
<div role="alert" aria-live="polite" aria-atomic="true">
{message}
</div>
// Error: aria-live="assertive"
Focus Management
Roving Tabindex
// Only focused item has tabIndex={0}
{items.map((item, i) => (
<button
key={item.id}
tabIndex={focusedIndex === i ? 0 : -1}
onKeyDown={(e) => handleArrowKeys(e, i)}
/>
))}
Focus Restoration
// Store trigger before modal opens
const triggerRef = useRef<HTMLElement>(null)
const openModal = (e) => { triggerRef.current = e.currentTarget; setOpen(true) }
const closeModal = () => { setOpen(false); triggerRef.current?.focus() }
Motion Preferences
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
Validation Workflow
Component A11y Testing with vitest-axe
Faster a11y validation than E2E:
// test/setup.ts
import * as axeMatchers from "vitest-axe/matchers"
import { expect } from "vitest"
expect.extend(axeMatchers)
// test/vitest-axe.d.ts (type definitions)
import "vitest"
import type { AxeMatchers } from "vitest-axe/matchers"
declare module "vitest" {
interface Assertion<_T> extends AxeMatchers {}
}
// accessibility.test.tsx
import { composeStories } from "@storybook/react"
import { render } from "@testing-library/react"
import { axe } from "vitest-axe"
import * as TreeViewStories from "./TreeView.stories"
const { Default } = composeStories(TreeViewStories)
test("TreeView axe-core check", async () => {
const { container } = render(<Default />)
const results = await axe(container)
expect(results).toHaveNoViolations()
})
Benefits:
- Faster than E2E (milliseconds vs seconds)
- Reuse Storybook stories
- Cover all components in CI/CD
Automated (CI)
# axe-core
npx @axe-core/cli <url> --tags wcag2a,wcag2aa,wcag22aa --exit
# Lighthouse
npx lighthouse <url> --only-categories=accessibility --output=json
# pa11y
npx pa11y <url> --standard WCAG2AA
Manual Checklist
- Keyboard: Tab through all controls, verify reachability
- Focus: Confirm visible 2px+ ring on every interactive element
- Screen reader: Test with NVDA/VoiceOver, verify announcements
- Zoom: Scale to 200%, verify no content loss
- Motion: Enable
prefers-reduced-motion, verify compliance - Contrast: Check text 4.5:1, UI components 3:1
- Color blindness: Test with vision deficiency emulation
Screen Reader Testing Guide
NVDA (Windows):
- Press
Insert+Spaceto toggle focus mode Insert+Downto read allHto navigate headings,Dfor landmarks- Verify: role, name, state announced correctly
VoiceOver (macOS):
Cmd+F5to enableVO+Ato read allVO+Uto open rotor (headings, links, landmarks)- Verify: proper navigation, state changes announced
Mobile (TalkBack/VoiceOver):
- Swipe right to move forward
- Double-tap to activate
- Verify: touch targets accessible, gestures work
Testable Assertions Template
## Component: [Name]
### WCAG Compliance
- [ ] SC 1.3.1: Semantic structure
- [ ] SC 2.1.1: Keyboard operable
- [ ] SC 2.4.7: Focus visible
- [ ] SC 4.1.2: Accessible name
### Screen Reader
Expected: "[Role]: [Name], [State]"