Accessibility
Build WCAG 2.1 AA compliant websites with semantic HTML, proper ARIA, focus management, and screen reader support. Includes color contrast (4.5:1 text), keyboard navigation, form labels, and live regions. Use when implementing accessible interfaces, fixing screen reader issues, keyboard navigation, or troubleshooting "focus outline missing", "aria-label required", "insufficient contrast".
安装 / 下载方式
TotalClaw CLI推荐
totalclaw install clawskills:veeramanikandanr48~accessibilitycURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/clawskills%3Aveeramanikandanr48~accessibility/file -o accessibility.mdGit 仓库获取源码
git clone https://github.com/clawdbot/skills/commit/a7b180b1fad04f625060d755333630a1615c6bb3# Web Accessibility (WCAG 2.1 AA)
**Status**: Production Ready ✅
**Last Updated**: 2026-01-14
**Dependencies**: None (framework-agnostic)
**Standards**: WCAG 2.1 Level AA
---
## Quick Start (5 Minutes)
### 1. Semantic HTML Foundation
Choose the right element - don't use `div` for everything:
```html
<!-- ❌ WRONG - divs with onClick -->
<div onclick="submit()">Submit</div>
<div onclick="navigate()">Next page</div>
<!-- ✅ CORRECT - semantic elements -->
<button type="submit">Submit</button>
<a href="/next">Next page</a>
```
**Why this matters:**
- Semantic elements have built-in keyboard support
- Screen readers announce role automatically
- Browser provides default accessible behaviors
### 2. Focus Management
Make interactive elements keyboard-accessible:
```css
/* ❌ WRONG - removes focus outline */
button:focus { outline: none; }
/* ✅ CORRECT - custom accessible outline */
button:focus-visible {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
```
**CRITICAL:**
- Never remove focus outlines without replacement
- Use `:focus-visible` to show only on keyboard focus
- Ensure 3:1 contrast ratio for focus indicators
### 3. Text Alternatives
Every non-text element needs a text alternative:
```html
<!-- ❌ WRONG - no alt text -->
<img src="logo.png">
<button><svg>...</svg></button>
<!-- ✅ CORRECT - proper alternatives -->
<img src="logo.png" alt="Company Name">
<button aria-label="Close dialog"><svg>...</svg></button>
```
---
## The 5-Step Accessibility Process
### Step 1: Choose Semantic HTML
**Decision tree for element selection:**
```
Need clickable element?
├─ Navigates to another page? → <a href="...">
├─ Submits form? → <button type="submit">
├─ Opens dialog? → <button aria-haspopup="dialog">
└─ Other action? → <button type="button">
Grouping content?
├─ Self-contained article? → <article>
├─ Thematic section? → <section>
├─ Navigation links? → <nav>
└─ Supplementary info? → <aside>
Form element?
├─ Text input? → <input type="text">
├─ Multiple choice? → <select> or <input type="radio">
├─ Toggle? → <input type="checkbox"> or <button aria-pressed>
└─ Long text? → <textarea>
```
**See `references/semantic-html.md` for complete guide.**
### Step 2: Add ARIA When Needed
**Golden rule: Use ARIA only when HTML can't express the pattern.**
```html
<!-- ❌ WRONG - unnecessary ARIA -->
<button role="button">Click me</button> <!-- Button already has role -->
<!-- ✅ CORRECT - ARIA fills semantic gap -->
<div role="dialog" aria-labelledby="title" aria-modal="true">
<h2 id="title">Confirm action</h2>
<!-- No HTML dialog yet, so role needed -->
</div>
<!-- ✅ BETTER - Use native HTML when available -->
<dialog aria-labelledby="title">
<h2 id="title">Confirm action</h2>
</dialog>
```
**Common ARIA patterns:**
- `aria-label` - When visible label doesn't exist
- `aria-labelledby` - Reference existing text as label
- `aria-describedby` - Additional description
- `aria-live` - Announce dynamic updates
- `aria-expanded` - Collapsible/expandable state
**See `references/aria-patterns.md` for complete patterns.**
### Step 3: Implement Keyboard Navigation
**All interactive elements must be keyboard-accessible:**
```typescript
// Tab order management
function Dialog({ onClose }) {
const dialogRef = useRef<HTMLDivElement>(null);
const previousFocus = useRef<HTMLElement | null>(null);
useEffect(() => {
// Save previous focus
previousFocus.current = document.activeElement as HTMLElement;
// Focus first element in dialog
const firstFocusable = dialogRef.current?.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
(firstFocusable as HTMLElement)?.focus();
// Trap focus within dialog
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
if (e.key === 'Tab') {
// Focus trap logic here
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
// Restore focus on close
previousFocus.current?.focus();
};
}, [onClose]);
return <div ref={dialogRef} role="dialog">...</div>;
}
```
**Essential keyboard patterns:**
- Tab/Shift+Tab: Navigate between focusable elements
- Enter/Space: Activate buttons/links
- Arrow keys: Navigate within components (tabs, menus)
- Escape: Close dialogs/menus
- Home/End: Jump to first/last item
**See `references/focus-management.md` for complete patterns.**
### Step 4: Ensure Color Contrast
**WCAG AA requirements:**
- Normal text (under 18pt): 4.5:1 contrast ratio
- Large text (18pt+ or 14pt+ bold): 3:1 contrast ratio
- UI components (buttons, borders): 3:1 contrast ratio
```css
/* ❌ WRONG - insufficient contrast */
:root {
--background: #ffffff;
--text: #999999; /* 2.8:1 - fails WCAG AA */
}
/* ✅ CORRECT - sufficient contrast */
:root {
--background: #ffffff;
--text: #595959; /* 4.6:1 - passes WCAG AA */
}
```
**Testing tools:**
- Browser DevTools (Chrome/Firefox have built-in checkers)
- Contrast checker extensions
- axe DevTools extension
**See `references/color-contrast.md` for complete guide.**
### Step 5: Make Forms Accessible
**Every form input needs a visible label:**
```html
<!-- ❌ WRONG - placeholder is not a label -->
<input type="email" placeholder="Email address">
<!-- ✅ CORRECT - proper label -->
<label for="email">Email address</label>
<input type="email" id="email" name="email" required aria-required="true">
```
**Error handling:**
```html
<label for="email">Email address</label>
<input
type="email"
id="email"
name="email"
aria-invalid="true"
aria-describedby="email-error"
>
<span id="email-error" role="alert">
Please enter a valid email address
</span>
```
**Live regions for dynamic errors:**
```html
<div role="alert" aria-live="assertive" aria-atomic="true">
Form submission failed. Please fix the errors above.
</div>
```
**See `references/forms-validation.md` for complete patterns.**
---
## Critical Rules
### Always Do
✅ Use semantic HTML elements first (button, a, nav, article, etc.)
✅ Provide text alternatives for all non-text content
✅ Ensure 4.5:1 contrast for normal text, 3:1 for large text/UI
✅ Make all functionality keyboard accessible
✅ Test with keyboard only (unplug mouse)
✅ Test with screen reader (NVDA on Windows, VoiceOver on Mac)
✅ Use proper heading hierarchy (h1 → h2 → h3, no skipping)
✅ Label all form inputs with visible labels
✅ Provide focus indicators (never just `outline: none`)
✅ Use `aria-live` for dynamic content updates
### Never Do
❌ Use `div` with `onClick` instead of `button`
❌ Remove focus outlines without replacement
❌ Use color alone to convey information
❌ Use placeholders as labels
❌ Skip heading levels (h1 → h3)
❌ Use `tabindex` > 0 (messes with natural order)
❌ Add ARIA when semantic HTML exists
❌ Forget to restore focus after closing dialogs
❌ Use `role="presentation"` on focusable elements
❌ Create keyboard traps (no way to escape)
---
## Known Issues Prevention
This skill prevents **12** documented accessibility issues:
### Issue #1: Missing Focus Indicators
**Error**: Interactive elements have no visible focus indicator
**Source**: WCAG 2.4.7 (Focus Visible)
**Why It Happens**: CSS reset removes default outline
**Prevention**: Always provide custom focus-visible styles
### Issue #2: Insufficient Color Contrast
**Error**: Text has less than 4.5:1 contrast ratio
**Source**: WCAG 1.4.3 (Contrast Minimum)
**Why It Happens**: Using light gray text on white background
**Prevention**: Test all text colors with contrast checker
### Issue #3: Missing Alt Text
**Error**: Images missing alt attributes
**Source**: WCAG 1.1.1 (Non-text Content)
**Why It Happens**: Forgot to add or thought it was optional
**Prevention**: Add alt="" for decorative, descriptive alt for meaningful images
### Issue #4: Keyboard Navigation Broken
**Error**: Interactive elements not reachable by k