wcag-audit-patterns by wshobson/agents
npx skills add https://github.com/wshobson/agents --skill wcag-audit-patterns遵循 WCAG 2.2 指南进行网页内容审计的综合指南,并提供可行的修复策略。
| 等级 | 描述 | 适用场景 |
|---|---|---|
| A | 最低无障碍性 | 法律基线 |
| AA | 标准符合性 | 大多数法规要求 |
| AAA | 增强的无障碍性 | 特殊需求 |
Perceivable: 用户能否感知内容?
Operable: 用户能否操作界面?
Understandable: 用户能否理解内容?
Robust: 能否与辅助技术协同工作?
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
Critical (阻塞性问题):
├── 功能性图片缺少替代文本
├── 交互元素无法通过键盘访问
├── 表单缺少标签
└── 自动播放的媒体没有控制选项
Serious (严重问题):
├── 颜色对比度不足
├── 缺少跳过链接
├── 自定义小部件无法访问
└── 缺少页面标题
Moderate (一般问题):
├── 缺少语言属性
├── 链接文本不清晰
├── 缺少地标区域
└── 标题层级结构不当
## 1.1 文本替代方案
### 1.1.1 非文本内容 (A 级)
- [ ] 所有图片都有替代文本
- [ ] 装饰性图片使用 alt=""
- [ ] 复杂图片有详细描述
- [ ] 有意义的图标具有可访问的名称
- [ ] CAPTCHA 有替代方案
检查:
```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" />
```
检查:
<!-- 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>
工具:WebAIM 对比度检查器,axe DevTools
增加间距时内容不丢失
行高为字体大小的 1.5 倍
段落间距为字体大小的 2 倍
字母间距为字体大小的 0.12 倍
单词间距为字体大小的 0.16 倍
## 2.1 键盘可访问
### 2.1.1 键盘 (A 级)
- [ ] 所有功能均可通过键盘访问
- [ ] 无键盘陷阱
- [ ] Tab 键顺序符合逻辑
- [ ] 自定义小部件可通过键盘操作
检查:
```javascript
// 自定义按钮必须可通过键盘访问
<div role="button" tabindex="0"
onkeydown="if(event.key === 'Enter' || event.key === ' ') activate()">
移动内容可以暂停
自动更新的内容可以暂停
动画遵循 prefers-reduced-motion 设置
@media (prefers-reduced-motion: reduce) {
存在"跳转到主要内容"链接
定义了地标区域
标题结构正确
<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; }
获得焦点的元素未被完全隐藏
粘性标题不遮挡焦点
## 3.1 可读性
### 3.1.1 页面语言 (A 级)
- [ ] HTML lang 属性已设置
- [ ] 语言与内容匹配
```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>法律/财务表单可撤销
提交前检查数据
用户可在提交前审阅
## 4.1 兼容性
### 4.1.1 解析 (A 级) - 在 WCAG 2.2 中已过时
- [ ] 有效的 HTML (良好实践)
- [ ] 无重复 ID
- [ ] 完整的开始/结束标签
### 4.1.2 名称、角色、值 (A 级)
- [ ] 自定义小部件具有可访问的名称
- [ ] ARIA 角色正确
- [ ] 状态变更被播报
```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>// axe-core 集成
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('should have no accessibility violations', async ({ page }) => {
await page.goto('/');
const results = await runAccessibilityAudit(page);
expect(results.violations).toHaveLength(0);
});
# CLI 工具
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 - 可见标签 -->
<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 对比度 */
.text {
color: #767676;
}
/* After: 4.5:1 对比度 */
.text {
color: #595959;
}
/* 或者添加背景 */
.text {
color: #767676;
background: #000;
}
// 使自定义元素可通过键盘访问
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;
}
});
}
}
每周安装量
3.6K
仓库
GitHub 星标
32.2K
首次出现
2026 年 1 月 20 日
安全审计
安装于
claude-code2.8K
gemini-cli2.7K
opencode2.7K
cursor2.6K
codex2.6K
github-copilot2.3K
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
No content loss with increased spacing
Line height 1.5x font size
Paragraph spacing 2x font size
Letter spacing 0.12x font size
Word spacing 0.16x font size
## 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()">
Moving content can be paused
Auto-updating content can be paused
Animations respect prefers-reduced-motion
@media (prefers-reduced-motion: reduce) {
Skip to main content link present
Landmark regions defined
Proper heading structure
<a href="#main" class="skip-link">Skip to main content</a>
<main id="main">...</main>Links make sense out of context
No "click here" or "read more" alone
<!-- Bad --><a href="report.pdf">Click here</a>
<!-- Good --><a href="report.pdf">Download Q4 Sales Report (PDF)</a>
Focus indicator visible on all elements
Custom focus styles meet contrast
:focus { outline: 3px solid #005fcc; outline-offset: 2px; }
Focused element not fully hidden
Sticky headers don't obscure focus
## 3.1 Readable
### 3.1.1 Language of Page (Level A)
- [ ] HTML lang attribute set
- [ ] Language correct for content
```html
<html lang="en">
Language changes marked
<p>The French word <span lang="fr">bonjour</span> means hello.</p>Errors clearly identified
Error message describes problem
Error linked to field
<input aria-describedby="email-error" aria-invalid="true" /> <span id="email-error" role="alert">Please enter valid email</span>Legal/financial forms reversible
Data checked before submission
User can review before submit
## 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>
Status updates announced
Live regions used correctly
<div role="status" aria-live="polite">3 items added to cart</div> <div role="alert" aria-live="assertive">Error: Form submission failed</div>// 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;
}
});
}
}
Weekly Installs
3.6K
Repository
GitHub Stars
32.2K
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
claude-code2.8K
gemini-cli2.7K
opencode2.7K
cursor2.6K
codex2.6K
github-copilot2.3K
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装