name: 无障碍模式
description: 遵循WCAG指南构建包容性网页体验。覆盖语义HTML、ARIA、键盘导航、颜色对比和测试策略。在无障碍、a11y、WCAG、屏幕阅读器或包容性设计请求时触发。
license: MIT
无障碍模式
从一开始就为所有人构建。
核心原则 (POUR)
| 原则 |
含义 |
示例 |
| 可感知 |
用户可以感知内容 |
替代文本、字幕、对比度 |
| 可操作 |
用户可以交互 |
键盘访问、足够时间 |
| 可理解 |
用户可以理解 |
清晰语言、可预测 |
| 健壮 |
与辅助技术兼容 |
有效的HTML、ARIA |
WCAG 级别
| 级别 |
描述 |
目标 |
| A |
最低要求 |
必须拥有 |
| AA |
标准 |
行业标准、法律要求 |
| AAA |
增强 |
最好有 |
目标级别 AA 适用于大多数项目。
语义HTML
使用正确的元素
| 替代使用 |
使用 |
<div onclick> |
<button> |
<span class="link"> |
<a href> |
<div class="header"> |
<header> |
<div class="nav"> |
<nav> |
<div class="main"> |
<main> |
<b> 用于强调 |
<strong> |
<i> 用于强调 |
<em> |
文档结构
<!DOCTYPE html>
<html lang="en">
<head>
<title>描述性页面标题</title>
</head>
<body>
<a href="#main" class="skip-link">跳过主要内容</a>
<header>
<nav aria-label="主导航">
<!-- 导航 -->
</nav>
</header>
<main id="main">
<h1>页面标题</h1>
<!-- 每个页面只有一个 h1 -->
<article>
<h2>章节</h2>
<h3>子章节</h3>
</article>
</main>
<aside aria-label="相关内容">
<!-- 侧边栏 -->
</aside>
<footer>
<!-- 页脚内容 -->
</footer>
</body>
</html>
标题层次结构
h1 - 页面标题(每个页面一个)
h2 - 主要章节
h3 - 子章节
h4 - 子子章节
切勿跳过级别(如 h1 → h3)
图像与媒体
替代文本
<!-- 信息性图像 -->
<img src="chart.png" alt="条形图显示第四季度销售额增长40%">
<!-- 装饰性图像 -->
<img src="decorative-border.png" alt="" role="presentation">
<!-- 复杂图像 -->
<figure>
<img src="complex-diagram.png" alt="系统架构图">
<figcaption>
系统架构的详细描述...
</figcaption>
</figure>
<!-- 图像作为链接 -->
<a href="/products">
<img src="product.jpg" alt="查看我们的产品">
</a>
替代文本指南
| 图像类型 |
替代文本策略 |
| 信息性 |
描述内容和功能 |
| 装饰性 |
空 alt=“” |
| 功能性 |
描述操作 |
| 复杂 |
简短alt + 较长描述 |
| 图像中的文本 |
包含所有文本 |
视频与音频
<!-- 带字幕的视频 -->
<video controls>
<source src="video.mp4" type="video/mp4">
<track kind="captions" src="captions.vtt" srclang="en" label="英语">
<track kind="descriptions" src="descriptions.vtt" srclang="en" label="音频描述">
</video>
<!-- 带转录的音频 -->
<audio controls>
<source src="podcast.mp3" type="audio/mpeg">
</audio>
<a href="transcript.html">阅读转录</a>
表单
标签
<!-- 显式标签(首选) -->
<label for="email">电子邮件地址</label>
<input type="email" id="email" name="email">
<!-- 隐式标签 -->
<label>
电子邮件地址
<input type="email" name="email">
</label>
<!-- 必填字段 -->
<label for="name">
姓名 <span aria-hidden="true">*</span>
<span class="sr-only">(必填)</span>
</label>
<input type="text" id="name" required aria-required="true">
错误处理
<div role="alert" aria-live="polite">
<p>请修复以下错误:</p>
<ul>
<li><a href="#email">电子邮件是必填项</a></li>
</ul>
</div>
<label for="email">电子邮件</label>
<input
type="email"
id="email"
aria-invalid="true"
aria-describedby="email-error"
>
<span id="email-error" class="error">请输入有效的电子邮件地址</span>
表单分组
<fieldset>
<legend>邮寄地址</legend>
<label for="street">街道</label>
<input type="text" id="street">
<label for="city">城市</label>
<input type="text" id="city">
</fieldset>
键盘导航
焦点管理
/* 永远不要移除焦点轮廓而不提供替代 */
:focus {
outline: 2px solid #005fcc;
outline-offset: 2px;
}
/* 自定义焦点样式 */
:focus-visible {
outline: 3px solid #005fcc;
outline-offset: 2px;
}
/* 对鼠标用户隐藏轮廓 */
:focus:not(:focus-visible) {
outline: none;
}
Tab 键顺序
<!-- 自然Tab键顺序遵循DOM顺序 -->
<!-- 仅在必要时使用 tabindex -->
<button>第一个</button>
<button>第二个</button>
<button>第三个</button>
<!-- tabindex="0" - 添加到Tab键顺序 -->
<div tabindex="0" role="button">自定义交互元素</div>
<!-- tabindex="-1" - 可通过JS聚焦,但不通过Tab键 -->
<div tabindex="-1" id="modal">模态内容</div>
<!-- 永远不要使用 tabindex > 0 -->
跳过链接
<a href="#main" class="skip-link">跳过主要内容</a>
<style>
.skip-link {
position: absolute;
top: -40px;
left: 0;
padding: 8px;
background: #000;
color: #fff;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
</style>
键盘模式
| 组件 |
按键 |
| 按钮 |
Enter, 空格 |
| 链接 |
Enter |
| 菜单 |
方向键, Enter, Escape |
| 选项卡 |
方向键, Tab |
| 模态 |
Tab(捕获), Escape 关闭 |
ARIA
何时使用ARIA
- 首先,使用语义HTML
- 然后,如果需要,添加ARIA
- “没有ARIA比坏ARIA更好”
常见ARIA模式
<!-- 实时区域(用于动态内容) -->
<div aria-live="polite">内容更新将宣布</div>
<div aria-live="assertive">紧急更新中断</div>
<!-- 展开/折叠 -->
<button aria-expanded="false" aria-controls="menu">菜单</button>
<ul id="menu" hidden>...</ul>
<!-- 当前页面 -->
<nav>
<a href="/" aria-current="page">首页</a>
<a href="/about">关于</a>
</nav>
<!-- 忙碌状态 -->
<div aria-busy="true">加载中...</div>
<!-- 对辅助技术隐藏 -->
<span aria-hidden="true">👍</span>
<!-- 标签 -->
<button aria-label="关闭">×</button>
<nav aria-label="主导航">...</nav>
<section aria-labelledby="section-heading">
<h2 id="section-heading">章节标题</h2>
</section>
ARIA 角色
<!-- 地标 -->
<div role="banner">页眉</div>
<div role="navigation">导航</div>
<div role="main">主体</div>
<div role="complementary">侧边栏</div>
<div role="contentinfo">页脚</div>
<!-- 小部件 -->
<div role="button">自定义按钮</div>
<div role="dialog" aria-modal="true">模态</div>
<div role="tablist">选项卡</div>
<div role="alert">错误消息</div>
颜色与对比度
对比度要求
| 文本大小 |
级别 AA |
级别 AAA |
| 普通文本 (<18px) |
4.5:1 |
7:1 |
| 大文本 (≥18px 粗体, ≥24px) |
3:1 |
4.5:1 |
| UI 组件 |
3:1 |
- |
颜色独立性
<!-- 不要仅依赖颜色 -->
<!-- 坏例子 -->
<span style="color: red">错误</span>
<!-- 好例子 -->
<span style="color: red">
⚠️ 错误: <span class="error-text">请输入有效的电子邮件</span>
</span>
测试工具
- WebAIM 对比度检查器
- Chrome DevTools 颜色选择器
- Stark(Figma 插件)
- axe DevTools
测试
自动化测试
// 使用 jest-axe
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
it('应该没有无障碍违规', async () => {
const { container } = render(<MyComponent />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
手动测试清单
- [ ] 仅用键盘导航整个页面
- [ ] 用屏幕阅读器测试(VoiceOver, NVDA)
- [ ] 检查颜色对比度
- [ ] 放大到200% - 仍然可用吗?
- [ ] 禁用CSS - 仍然可理解吗?
- [ ] 检查所有图像是否有替代文本
- [ ] 验证表单标签和错误
- [ ] 测试焦点可见性
- [ ] 检查标题结构
屏幕阅读器测试
| 操作系统 |
屏幕阅读器 |
浏览器 |
| macOS |
VoiceOver |
Safari |
| Windows |
NVDA |
Firefox |
| Windows |
JAWS |
Chrome |
| 移动端 |
TalkBack |
Chrome |
| iOS |
VoiceOver |
Safari |
常见模式
模态对话框
<div
role="dialog"
aria-modal="true"
aria-labelledby="dialog-title"
aria-describedby="dialog-desc"
>
<h2 id="dialog-title">确认操作</h2>
<p id="dialog-desc">您确定要继续吗?</p>
<button>取消</button>
<button>确认</button>
</div>
焦点管理:
- 打开时移动焦点到模态
- 将焦点捕获在模态内
- 关闭时返回焦点到触发器
选项卡
<div role="tablist" aria-label="设置">
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">
常规
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2">
安全
</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
常规设置内容
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
安全设置内容
</div>
参考资料
references/wcag-checklist.md - 完整的 WCAG 2.1 清单
references/aria-patterns.md - 常见 ARIA 模式
references/testing-tools.md - 测试工具设置