# 无障碍检查清单

WCAG 2.1 AA 的快速参考。可与 `frontend-ui-engineering` skill 配合使用。

## 目录

- [核心检查](#核心检查)
- [常见 HTML 模式](#常见-html-模式)
- [测试工具](#测试工具)
- [快速参考：ARIA live regions](#快速参考aria-live-regions)
- [常见反模式](#常见反模式)

## 核心检查

### 键盘导航
- [ ] 所有交互元素都能通过 Tab 键聚焦
- [ ] 焦点顺序符合视觉 / 逻辑顺序
- [ ] 焦点可见（聚焦元素有 outline / ring）
- [ ] 自定义组件支持键盘操作（Enter 激活，Escape 关闭）
- [ ] 没有键盘陷阱（用户总能 Tab 离开某个组件）
- [ ] 页面顶部有跳过内容链接
- [ ] Modal 打开时会把焦点锁住，关闭时会把焦点还回去

### 屏幕阅读器
- [ ] 所有图片都有 `alt` 文本（装饰性图片可用 `alt=""`）
- [ ] 所有表单输入都有对应标签（`<label>` 或 `aria-label`）
- [ ] 按钮和链接有描述性文字（不要写 “Click here”）
- [ ] 只有图标的按钮有 `aria-label`
- [ ] 页面只有一个 `<h1>`，标题层级不跳级
- [ ] 动态内容变化会被播报（`aria-live` region）
- [ ] 表格有带 `scope` 的 `<th>` 表头

### 视觉
- [ ] 文本对比度 ≥ 4.5:1（普通文本）或 ≥ 3:1（大号文本，18px+）
- [ ] UI 组件与背景的对比度 ≥ 3:1
- [ ] 颜色不是传达信息的唯一方式
- [ ] 文本放大到 200% 后布局不会坏
- [ ] 没有每秒闪烁超过 3 次的内容

### 表单
- [ ] 每个输入框都有可见标签
- [ ] 必填项有明确标记（不能只靠颜色）
- [ ] 错误消息具体，并且与字段关联
- [ ] 错误状态不只是靠颜色表达（图标、文字、边框都可以）
- [ ] 表单提交错误有汇总，并且可以聚焦

### 内容
- [ ] 已声明语言（`<html lang="en">`）
- [ ] 页面有描述性 `<title>`
- [ ] 链接与周围文字有明显区分（不只是靠颜色）
- [ ] 移动端触控目标 ≥ 44x44px
- [ ] 空状态有意义（不要空白页）

## 常见 HTML 模式

### 按钮 vs 链接

```html
<!-- 行为用 <button> -->
<button onClick={handleDelete}>Delete Task</button>

<!-- 导航用 <a> -->
<a href="/tasks/123">View Task</a>

<!-- 永远不要把 div/span 当按钮 -->
<div onClick={handleDelete}>Delete</div>  <!-- BAD -->
```

### 表单标签

```html
<!-- 显式关联标签 -->
<label htmlFor="email">Email address</label>
<input id="email" type="email" required />

<!-- 隐式包裹 -->
<label>
  Email address
  <input type="email" required />
</label>

<!-- 隐藏标签（优先使用可见标签） -->
<input type="search" aria-label="Search tasks" />
```

### ARIA Roles

```html
<!-- 导航 -->
<nav aria-label="Main navigation">...</nav>
<nav aria-label="Footer links">...</nav>

<!-- 状态消息 -->
<div role="status" aria-live="polite">Task saved</div>

<!-- 警报消息 -->
<div role="alert">Error: Title is required</div>

<!-- 模态框 -->
<dialog aria-modal="true" aria-labelledby="dialog-title">
  <h2 id="dialog-title">Confirm Delete</h2>
  ...
</dialog>

<!-- 加载状态 -->
<div aria-busy="true" aria-label="Loading tasks">
  <Spinner />
</div>
```

### 可访问列表

```html
<ul role="list" aria-label="Tasks">
  <li>
    <input type="checkbox" id="task-1" aria-label="Complete: Buy groceries" />
    <label htmlFor="task-1">Buy groceries</label>
  </li>
</ul>
```

## 测试工具

```bash
# 自动化审计
npx axe-core          # 程序化无障碍测试
npx pa11y             # CLI 无障碍检查器

# 浏览器里
# Chrome DevTools → Lighthouse → Accessibility
# Chrome DevTools → Elements → Accessibility tree

# 屏幕阅读器测试
# macOS: VoiceOver (Cmd + F5)
# Windows: NVDA (free) or JAWS
# Linux: Orca
```

## 快速参考：ARIA Live Regions

| Value | Behavior | Use For |
|-------|----------|---------|
| `aria-live="polite"` | 在下一个停顿时播报 | 状态更新、保存成功提示 |
| `aria-live="assertive"` | 立即播报 | 错误、时效性很强的提醒 |
| `role="status"` | 同 `polite` | 状态消息 |
| `role="alert"` | 同 `assertive` | 错误消息 |

## 常见反模式

| 反模式 | 问题 | 修复 |
|---|---|---|
| `div` 当按钮 | 不可聚焦、没有键盘支持 | 用 `<button>` |
| 缺少 `alt` 文本 | 屏幕阅读器看不到图片内容 | 加描述性 `alt` |
| 只靠颜色表达状态 | 色盲用户看不出来 | 加图标、文字或模式 |
| 自动播放媒体 | 让人不适，而且不能停 | 加控制，不要自动播放 |
| 没有 ARIA 的自定义下拉 | 键盘 / 屏幕阅读器不可用 | 用原生 `<select>` 或正确的 ARIA listbox |
| 去掉 focus outline | 用户看不出自己在哪 | 给 outline 做样式，不要移除 |
| 空链接 / 空按钮 | 只会被读成“Link”却没有说明 | 加文字或 `aria-label` |
| `tabindex > 0` | 打乱自然 tab 顺序 | 只用 `tabindex="0"` 或 `-1` |
