Entry/Exit
- Entry: Build UI components, forms, navigation, or interactive elements
- Exit: Components that pass a11y checks
- Points to: After a11y verification passes, continue build or enter
/review - Preload: CANON.md +
build-frontend-ui-engineering/SKILL.md - Output path: Components that pass a11y checks →
verify-workflow-review(enter review) or continue build
When not to use
- The change does not involve user-interactive UI, forms, navigation, or visual presentation
- It is only pure copy, backend, scripts, or data migration
- The UI must be implemented first, and there is currently no inspectable interface
Iron Law
Goal: WCAG 2.1 AA
AA is the minimum commercial standard to meet. AAA is strived for in specific areas but is not a global requirement.
Three pillars
1. Keyboard navigation
每个交互元素必须通过键盘可达和可操作:
├── Tab → 进入元素
├── Shift+Tab → 离开元素(回到上一个)
├── Enter / Space → 激活按钮/链接
├── Escape → 关闭模态框/下拉
├── Arrow Keys → 在选项间移动(菜单、tabs、单选组)
└── 焦点顺序 → 逻辑顺序(DOM 顺序匹配视觉顺序)
// Bad: div 不可通过键盘操作
<div onClick={handleClick}>删除</div>
// Good: button 自动有键盘支持
<button onClick={handleClick}>删除</button>
Focus trap: Modal opens → focus is trapped inside the modal (Tab cycle does not escape). Modal closes → focus returns to the trigger element.
2. Screen readers
// 图像: 始终有 alt
<img src="logo.png" alt="Company Name" />
// 装饰性图像: alt="" (空,不是缺失)
// 图标按钮: aria-label
<button aria-label="关闭对话框">
<XIcon />
</button>
// 表单: label 和 input 关联
<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 rule: Native HTML elements (<button>、<input>、<a>) already have implicit ARIA. No need to add role="button" to <button>. ARIA is only needed when native elements are not used.
3. Visual
对比度 (WCAG AA):
├── 普通文本: ≥ 4.5:1
├── 大文本 (≥18px bold 或 ≥24px): ≥ 3:1
└── UI 组件/图形: ≥ 3:1
颜色不是唯一的信息传达方式:
├── 错误状态: 红色边框 + 图标 + 文字
├── 成功状态: 绿色边框 + 图标 + 文字
├── 链接: 下划线(不仅仅是颜色)
└── 图表: 图案/纹理 + 颜色
Key checks
Forms
- Every input has a label
- Required fields have an indicator (asterisk + "required" text, not color alone)
- Error messages are associated with the corresponding input (
aria-describedby) - On validation error before submission, focus jumps to the first error field
Images
- Content images have descriptive
alt - Decorative images have
alt="" - SVG icons have
aria-labelor are hidden witharia-hidden="true"hidden
Navigation
- The page has a skip-to-content link
- The current page/location has
aria-current="page" - Main navigation has
<nav>landmark
Page structure
- Has a semantic heading hierarchy (h1 → h2 → h3, no skipped levels)
- Has
<main>、<header>、<footer>landmarks - Information does not rely on purely visual presentation ("right panel", "green button")
Dynamic content
- Toast/notifications have
role="alert"oraria-live - Loading states are announced (
aria-busyor loading prompt) - When a modal opens, focus moves into the modal
Testing tools
# 自动检查
npx axe-core # 静态分析
npx pa11y-ci # CI 集成
npx lighthouse # Chrome DevTools 中的 Accessibility 审计
# 手动测试
Tab 遍历页面 # 键盘导航是否有逻辑顺序?
VoiceOver / NVDA # 屏幕阅读器能否理解页面?
Common claims
| Claim | Reality | Consequence |
|---|---|---|
| "Add accessibility last" | Start from the first component. Add last = never add = rewrite everything. | Add last = the global DOM structure does not support keyboard navigation and ARIA, and the rework effort = rewriting 40-60% of the frontend. The project will never do it. |
| "Users are basically not people with disabilities" | Accessibility also serves everyone: keyboard users, mobile, slow networks, temporary impairments (broken arm). (Accessibility improves the experience for all users) | About 15% of the global population has some form of disability. Ignoring a11y = excluding 15% of potential users + legal compliance risk (ADA lawsuits average $50K+ in damages). |
| "As long as the automated checks pass, it's fine" | aXe can detect about 30% of issues. Manual keyboard testing and manual screen reader testing are irreplaceable. | The 70% that automated tools miss includes focus traps, semantic structure, and screen reader experience—these are the daily obstacles for real users. |
| "Using div is more convenient than using button" | A div has no keyboard operation, no ARIA role, and no focus management. Use button. | A button made with a div is completely inoperable for keyboard users and completely invisible to screen reader users—the functionality does not exist for them. |
| "Color distinction is enough" | About 8% of men have some form of color vision deficiency. Information should never be conveyed by color alone. | Users with color vision deficiency cannot see red error prompts → submit incorrect data → business process interruption. Distinction by color alone = functionality fails for 8% of users. |
Violating the letter of the rule is violating the spirit. There is no gray area.
Output template
# 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 顺序有逻辑性,所有交互元素可达
- [ ] 模态框焦点管理正确(进入+关回)
- [ ] 所有内容图像有 `alt`
- [ ] 颜色不是唯一信息传达方式
- [ ] 表单有 label、错误关联、必填指示
- [ ] aXe / Lighthouse Accessibility 审计通过(得分 ≥ 90)
- [ ] 键盘能完成主要用户流程