| name | verify-frontend-accessibility |
| description | 可访问性保障——WCAG 2.1 AA 合规。当构建 UI 组件、表单、导航或交互元素需要无障碍验证,或提到"无障碍""a11y""WCAG""ARIA" |
Accessibility — 可访问性保障
入口/出口
- 入口: 构建 UI 组件、表单、导航、或交互元素
- 出口: 通过 a11y 检查的组件
- 指向: a11y 验证通过后继续 build 或进入
/review
- 前置加载: CANON.md +
build-frontend-ui-engineering/SKILL.md
- 输出路径: 通过 a11y 检查的组件 →
verify-workflow-review(进入审查)或继续 build
何时不使用
- 变更不涉及用户可交互 UI、表单、导航或视觉呈现
- 只是纯文案、后端、脚本或数据迁移
- 需要先实现 UI,当前还没有可检查的界面
Iron Law
```
每个 UI 元素必须可被键盘访问、屏幕阅读器理解和视觉辨识。
WCAG 2.1 AA 是最低准入门槛,不是锦上添花。
没有通过 a11y 审查的 UI = 不可交付的 UI。
```
目标: WCAG 2.1 AA
AA 是达到的最低商业标准。AAA 在特定方面努力但不作为全局要求。
三大支柱
1. 键盘导航
每个交互元素必须通过键盘可达和可操作:
├── Tab → 进入元素
├── Shift+Tab → 离开元素(回到上一个)
├── Enter / Space → 激活按钮/链接
├── Escape → 关闭模态框/下拉
├── Arrow Keys → 在选项间移动(菜单、tabs、单选组)
└── 焦点顺序 → 逻辑顺序(DOM 顺序匹配视觉顺序)
<div onClick={handleClick}>删除</div>
<button onClick={handleClick}>删除</button>
焦点陷阱: 模态框打开 → 焦点困在模态框内(Tab 循环不逃逸)。模态框关闭 → 焦点回到触发元素。
2. 屏幕阅读器
<img src="logo.png" alt="Company Name" />
<button aria-label="关闭对话框">
<XIcon />
</button>
<label htmlFor="task-title">任务标题</label>
<input id="task-title" type="text" />
<input id="email" aria-describedby="email-error" />
<span id="email-error" role="alert">请输入有效的邮箱地址</span>
ARIA 规则: 原生 HTML 元素(<button>、<input>、<a>)已有隐式 ARIA。不需要加 role="button" 到 <button>。ARIA 仅在不使用原生元素时才需要。
3. 视觉
对比度 (WCAG AA):
├── 普通文本: ≥ 4.5:1
├── 大文本 (≥18px bold 或 ≥24px): ≥ 3:1
└── UI 组件/图形: ≥ 3:1
颜色不是唯一的信息传达方式:
├── 错误状态: 红色边框 + 图标 + 文字
├── 成功状态: 绿色边框 + 图标 + 文字
├── 链接: 下划线(不仅仅是颜色)
└── 图表: 图案/纹理 + 颜色
关键检查
表单
图像
导航
页面结构
动态内容
测试工具
npx axe-core
npx pa11y-ci
npx lighthouse
Tab 遍历页面
VoiceOver / NVDA
常见说辞
| 说辞 | 现实 | 后果 |
|---|
| "可访问性最后加" | 从第一个组件做起。最后加 = 永远不会加 = 重新写一遍。 | 最后加 = 全局 DOM 结构不支持键盘导航和 ARIA,改造工作量 = 重写前端 40-60%。项目永远不会做。 |
| "用户基本没有残障人士" | 可访问性也服务所有人:键盘用户、移动端、慢网络、临时障碍(手臂骨折)。(可访问性提升所有用户的体验) | 全球 ~15% 人口有某种形式的残障。忽略 a11y = 排除 15% 潜在用户 + 法律合规风险(ADA 诉讼平均赔偿 $50K+)。 |
| "自动检查通过了就行" | aXe 能检测 ~30% 的问题。键盘手动测试和屏幕阅读器手动测试不可替代。 | 自动工具遗漏的 70% 包括焦点陷阱、语义结构、屏幕阅读器体验——这些是真实用户的日常障碍。 |
| "用 div 比用 button 方便" | div 没有键盘操作、没有 ARIA role、没有焦点管理。用 button。 | div 做的按钮对键盘用户完全不可操作,对屏幕阅读器用户完全不可见——功能对他们来说不存在。 |
| "颜色区分足够" | ~8% 的男性有某种形式的色弱。信息永远不只通过颜色传达。 | 色弱用户看不到红色错误提示 → 提交错误数据 → 业务流程中断。纯颜色区分 = 对 8% 用户功能失效。 |
违反字面规则就是违反精神。 没有灰色地带。
输出模板
# Accessibility Audit Report — 可访问性审计报告
## 元信息
- **审查者**: [姓名/角色]
- **组件/页面**: [组件名或页面路径]
- **审查日期**: YYYY-MM-DD
## 自动检查结果
- **aXe**: [通过 / 发现 N 个违规] — 违规列表见下方
- **Lighthouse Accessibility**: [分数] — 最低准入: 90
- **pa11y-ci**: [通过 / 发现 N 个问题]
## 自动检查违规详情
| # | 规则 | 级别 | 位置 | 描述 | 修复状态 |
|---|------|------|------|------|---------|
| 1 | color-contrast | Critical | L42 | 文本对比度 2.8:1 < 4.5:1 | 🔴 待修复 |
| 2 | label | Critical | L78 | input #email 缺少 label | 🔴 待修复 |
| 3 | aria-valid-attr | Important | L91 | aria-label 值为空字符串 | 🟡 建议修复 |
## 手动检查结果
### 键盘导航
- [ ] Tab 顺序逻辑性: [通过 / 未通过 — 具体问题]
- [ ] 模态框焦点管理: [通过 / 未通过 — 具体问题]
- [ ] 所有交互元素键盘可达: [通过 / 未通过 — 具体问题]
### 屏幕阅读器
- [ ] 语义结构完整: [通过 / 未通过 — 具体问题]
- [ ] ARIA 标注正确: [通过 / 未通过 — 具体问题]
- [ ] 动态内容宣告: [通过 / 未通过 — 具体问题]
### 视觉
- [ ] 对比度达标: [通过 / 未通过 — 具体问题]
- [ ] 颜色不是唯一信息方式: [通过 / 未通过 — 具体问题]
## 审查结论
- **通过**: 自动检查评分 ≥ 90 + 手动检查全部通过
- **阻止**: Critical 违规未修复或手动检查关键流程失败
## 红旗 — STOP
- 只用颜色(红/绿)指示状态 —— 没文字、没图标
- 表单 input 没有 label(或 placeholder 作为唯一标签)
- 模态框打开后焦点没有移动进去
- Tab 顺序在视觉和逻辑上不一致
- 图像没有 alt(且不是装饰性的)
- 视频没有字幕
- `onClick` div 充当按钮(用 `<button>`)
## 验证失败处理
| 失败场景 | 处理方式 |
|---------|---------|
| aXe/Lighthouse 评分 < 90 | 标记具体违规项。逐条修复后重新运行自动检查。不得降级准入标准。 |
| 键盘无法完成主要流程 | 阻止交付。检查 Tab 顺序、焦点陷阱、交互元素键盘操作。修复后手动验证完整流程。 |
| 模态框焦点未正确管理 | 阻止交付。打开时焦点必须移入模态框、Tab 循环不逃逸、关闭时焦点回到触发元素。 |
| 表单 input 缺少 label | 阻止交付。每个 input 必须有 visible label 或 aria-label。placeholder 不可作为唯一标签。 |
| 颜色对比度 < 4.5:1(普通文本)| 调整颜色或增加辅助标识(图标、文字、边框)。对比度不达标 = 信息不可辨识。 |
[ ] Tab 顺序有逻辑性,所有交互元素可达
[ ] 模态框焦点管理正确(进入+关回)
[ ] 所有内容图像有
[ ] 颜色不是唯一信息传达方式
[ ] 表单有 label、错误关联、必填指示
[ ] aXe / Lighthouse Accessibility 审计通过(得分 ≥ 90)
[ ] 键盘能完成主要用户流程