关于 wcag-design
WCAG-Design 是供前端团队创建无障碍界面的设计技能,介绍 WCAG 2.2 AA 原则与成功标准,涵盖感知、操作、理解和健壮性。
主要功能
- 01WCAG 2.2 AA 指南
- 02POUR 四项原则
- 03成功标准参考
- 04内容与控件的无障碍指导
适用场景
- 01按 WCAG 标准检查界面
- 02规划键盘可操作的导航
- 03检查对比度、文本替代和重排
- 04改进表单标签与错误处理
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ alexsnchz/ skills --skill wcag-design安装后在 Claude Code 中输入/wcag-design确认已加载。
附带文件README.md
详细介绍
WCAG-Design 技能
此技能指导创建符合 WCAG 2.2 AA 标准的无障碍、视觉独特的前端界面。无障碍与设计质量并不矛盾——最出色的无障碍界面同样设计精美。
第 1 部分 — WCAG 基础知识
什么是 WCAG?
**Web Content Accessibility Guidelines(网页内容无障碍指南,WCAG)**是全球通用的网页无障碍标准,由 W3C 通过 Web Accessibility Initiative(网页无障碍倡议,WAI)发布。WCAG 规定了如何让残障人士也能访问网页内容,其中包括盲人、低视力人士、聋人、听力受损人士、行动不便人士、光敏人士、言语障碍人士以及有认知或学习障碍的人士。
版本历史
| 版本 | 年份 | 说明 |
|---|---|---|
| WCAG 1.0 | 1999 | 首个 W3C 无障碍建议标准 |
| WCAG 2.0 | 2008 | 进行了重大重构,加入可测试的成功标准 |
| WCAG 2.1 | 2018 | +17 项新标准,涵盖移动设备、低视力和认知障碍 |
| WCAG 2.2 | 2023 | +9 项新标准,移除 4.1.1 Parsing;当前标准 |
| WCAG 3.0 | ~2028 | 草案;采用通俗语言和以结果为导向的模型 |
向后兼容性:符合 WCAG 2.2 的内容也符合 2.1 和 2.0。
第 2 部分 — POUR 四项原则
每项 WCAG 成功标准都对应以下四项原则之一:
1. 可感知(P)
所有信息和 UI 组件都必须以用户能够感知的方式呈现。
- 为非文本内容提供文本替代
- 为媒体提供字幕和音频描述
- 内容呈现方式应可调整
- 确保足够的色彩对比度和可读性
2. 可操作(O)
所有 UI 组件和导航都必须可操作。
- 完全支持键盘操作
- 不设置键盘陷阱
- 为交互提供充足的时间
- 不包含可能诱发癫痫的内容
- 支持多种输入方式(键盘、触控、语音)
- 提供无障碍焦点管理
3. 可理解(U)
用户必须能够理解内容以及界面的工作方式。
- 文本应易读且易于理解
- 行为应可预测且一致
- 提供输入辅助和错误恢复机制
- 使用清晰的语言和定义
4. 健壮(R)
内容必须能够与当前及未来的技术可靠地协同工作,尤其是辅助技术。
- 标记有效且格式规范
- 与浏览器和辅助技术兼容
- 语义结构恰当
第 3 部分 — 符合性等级
| 等级 | 标准数量 | 说明 |
|---|---|---|
| A | 32 | 最低级别——基本无障碍,满足基础要求 |
| AA | +24(共 56 项) | 推荐级别——涵盖大多数法律要求;解决常见障碍 |
| AAA | +31(共 87 项) | 增强级别——最高级别;并非总是必需 |
目标:WCAG 2.2 AA——这是大多数无障碍法律要求的级别(ADA、EN 301 549、Section 508、《安大略省残障人士无障碍法》等)。
第 4 部分 — WCAG 2.2 完整成功标准参考
原则 1:可感知
指南 1.1 — 文本替代
| 标准 | 等级 | 规则 |
|---|---|---|
| 1.1.1 非文本内容 | A | 所有非文本内容均有文本替代(图像、图标、图表、按钮) |
指南 1.2 — 基于时间的媒体
| 标准 | 等级 | 规则 |
|---|---|---|
| 1.2.1 纯音频和纯视频(预录制) | A | 提供文字稿或音频描述 |
| 1.2.2 字幕(预录制) | A | 为多媒体中的所有预录制音频提供字幕 |
| 1.2.3 音频描述或媒体替代内容(预录制) | A | 提供音频描述或完整的文本替代内容 |
| 1.2.4 字幕(实时) | AA | 为所有实时多媒体提供字幕 |
| 1.2.5 音频描述(预录制) | AA | 为所有预录制视频提供音频描述 |
| 1.2.6 手语(预录制) | AAA | 为预录制音频提供手语翻译 |
| 1.2.7 扩展音频描述(预录制) | AAA | 暂停不足时提供扩展音频描述 |
| 1.2.8 媒体替代内容(预录制) | AAA | 为所有预录制媒体提供完整的文本替代内容 |
| 1.2.9 纯音频(实时) | AAA | 为所有实时纯音频内容提供替代内容 |
指南 1.3 — 可适应
| 标准 | 等级 | 规则 |
|---|---|---|
| 1.3.1 信息和关系 | A | 必须能够通过编程确定通过呈现方式传达的结构和关系 |
| 1.3.2 有意义的顺序 | A | 阅读和导航顺序应合乎逻辑 |
| 1.3.3 感官特征 | A | 指示不能仅依赖颜色、大小、形状、视觉位置或声音 |
| 1.3.4 方向 | AA | 内容不受纵向或横向屏幕方向限制 |
| 1.3.5 确定输入目的 | AA | 输入字段的用途可通过编程识别(自动完成) |
| 1.3.6 确定用途 | AAA | 按钮、链接和控件的用途可通过编程确定 |
指南 1.4 — 可辨识
| 标准 | 等级 | 规则 |
|---|---|---|
| 1.4.1 颜色的使用 | A | 颜色不能是传达信息的唯一视觉方式 |
| 1.4.2 音频控制 | A | 可暂停、停止自动播放的音频,或控制其音量 |
| 1.4.3 对比度(最低) | AA | 普通文本为 4.5:1;大号文本为 3:1 |
| 1.4.4 调整文本大小 | AA | 文本可放大至 200%,且不损失内容 |
| 1.4.5 文本图像 | AA | 尽可能使用实际文本,而非文本图像 |
| 1.4.6 对比度(增强) | AAA | 普通文本为 7:1;大号文本为 4.5:1 |
| 1.4.7 低背景音或无背景音 | AAA | 纯音频内容应能清晰地从背景音中听辨 |
| 1.4.8 视觉呈现 | AAA | 限制文本宽度、可调整行距,且不使用两端对齐 |
| 1.4.9 文本图像(无例外) | AAA | 除徽标外,不使用文本图像 |
| 1.4.10 重排 | AA | 内容在宽度为 320px 的屏幕上无需水平滚动即可呈现 |
| 1.4.11 非文本对比度 | AA | UI 组件和图形元素的对比度至少为 3:1 |
| 1.4.12 文本间距 | AA | 行高 ≥1.5×、字间距 ≥0.12em、词间距 ≥0.16em 时,内容不应丢失 |
| 1.4.13 悬停或聚焦时的内容 | AA | 可悬停或可聚焦的内容可关闭、可悬停且持续显示 |
原则 2:可操作
指南 2.1 — 键盘可访问
| 标准 | 等级 | 规则 |
|---|---|---|
| 2.1.1 键盘 | A | 所有功能均可通过键盘操作 |
| 2.1.2 无键盘陷阱 | A | 用户可以使用键盘离开任何组件 |
| 2.1.3 键盘(无例外) | AAA | 所有功能均可通过键盘操作——无一例外 |
| 2.1.4 字符键快捷键 | A | 可禁用或重新映射单字符快捷键 |
指南 2.2 — 时间充足
| 标准 | 等级 | 规则 |
|---|---|---|
| 2.2.1 可调整时限 | A | 可关闭、调整或延长时限(≥默认时限的 10 倍) |
| 2.2.2 暂停、停止、隐藏 | A | 可暂停、停止或隐藏自动移动、闪烁或滚动的内容 |
| 2.2.3 无时限 | AAA | 除实时事件外,不设置时间限制 |
| 2.2.4 中断 | AAA | 用户可以推迟或屏蔽所有中断 |
| 2.2.5 重新进行身份验证 | AAA | 超时后重新进行身份验证时不会丢失数据 |
| 2.2.6 超时 | AAA | 在会话因不活动而超时前向用户发出警告 |
指南 2.3 — 癫痫发作和生理反应
| 标准 | 等级 | 规则 |
|---|---|---|
| 2.3.1 闪烁不超过三次或低于阈值 | A | 内容闪烁频率不得超过每秒 3 次(或低于阈值) |
| 2.3.2 三次闪烁 | AAA | 所有内容的闪烁频率不得超过每秒 3 次 |
| 2.3.3 交互触发的动画 | AAA | 可禁用由交互触发的运动动画 |
指南 2.4 — 可导航
| 标准 | 等级 | 规则 |
|---|---|---|
| 2.4.1 绕过区块 | A | 提供跳过重复区块的机制(跳过导航链接) |
| 2.4.2 页面标题 | A | 网页具有描述性标题 |
| 2.4.3 焦点顺序 | A | 焦点顺序合乎逻辑且有意义 |
| 2.4.4 链接用途(结合上下文) | A | 可从链接文本或上下文中明确了解链接用途 |
| 2.4.5 多种方式 | AA | 提供多种查找页面的方式(搜索、站点地图、导航) |
| 2.4.6 标题和标签 | AA | 标题和标签描述主题或用途 |
| 2.4.7 焦点可见 | AA | 键盘焦点指示器清晰可见 |
| 2.4.8 位置 | AAA | 用户可以了解自己在一组页面中的位置 |
| 2.4.9 链接用途(仅链接文本) | AAA | 仅从链接文本即可明确了解链接用途 |
| 2.4.10 区段标题 | AAA | 区段标题用于组织内容 |
| 2.4.11 焦点未被遮挡(最低要求) ⭐ 2.2 新增 | AA | 焦点元素未被粘性或固定内容完全遮挡 |
| 2.4.12 焦点未被遮挡(增强要求) ⭐ 2.2 新增 | AAA | 焦点元素完全可见 |
| 2.4.13 焦点外观 ⭐ 2.2 新增 | AAA | 焦点指示器:轮廓线 ≥2px,对比度变化 ≥3:1 |
指南 2.5 — 输入方式
| 标准 | 等级 | 规则 |
|---|---|---|
| 2.5.1 指针手势 | A | 基于路径的手势提供单指替代方式 |
| 2.5.2 指针取消 | A | 指针操作在“抬起”事件时激活,而非“按下”时 |
| 2.5.3 名称中的标签 | A | 无障碍名称包含可见标签文本 |
| 2.5.4 运动触发 | A | 运动触发的功能提供 UI 替代方式,且可禁用 |
| 2.5.5 目标尺寸(增强要求) | AAA | 触控目标尺寸 ≥44×44 CSS 像素 |
| 2.5.6 并发输入机制 | AAA | 不限制输入方式 |
| 2.5.7 拖动操作 ⭐ 2.2 新增 | AA | 仅支持拖动的功能提供单指替代方式 |
| 2.5.8 目标尺寸(最低要求) ⭐ 2.2 新增 | AA | 触控目标尺寸 ≥24×24 CSS 像素,或具有足够间距 |
原则 3:易于理解
指南 3.1 — 易读
| 标准 | 等级 | 规则 |
|---|---|---|
| 3.1.1 页面语言 | A | 通过程序识别默认的人类语言(lang 属性) |
| 3.1.2 内容部分的语言 | AA | 标记内容中的语言变化 |
| 3.1.3 不常见词语 | AAA | 为行话、习语和技术术语提供定义 |
| 3.1.4 缩写 | AAA | 首次使用时展开缩写 |
| 3.1.5 阅读难度 | AAA | 提供适合初中低年级阅读水平的文本,或提供简化版本 |
| 3.1.6 发音 | AAA | 为有歧义的词语提供发音说明 |
指南 3.2 — 可预测
| 标准 | 等级 | 规则 |
|---|---|---|
| 3.2.1 获得焦点时 | A | 组件获得焦点时,上下文不发生变化 |
| 3.2.2 输入时 | A | 更改设置不会自动提交表单 |
| 3.2.3 一致的导航 | AA | 各页面上的导航机制顺序保持相对一致 |
| 3.2.4 一致的标识 | AA | 功能相同的组件使用一致的标识 |
| 3.2.5 按请求更改 | AAA | 仅在用户请求时更改上下文 |
| 3.2.6 一致的帮助 ⭐ 2.2 新增 | A | 各页面上的帮助机制位于相同位置 |
指南 3.3 — 输入辅助
| 标准 | 等级 | 规则 |
|---|---|---|
| 3.3.1 错误识别 | A | 以文本形式识别并描述错误 |
| 3.3.2 标签或说明 | A | 为所有输入提供标签或说明 |
| 3.3.3 错误建议 | AA | 在已知情况下,为输入错误提供建议 |
| 3.3.4 错误预防(法律、财务、数据) | AA | 关键提交操作可撤销、检查或确认 |
| 3.3.5 帮助 | AAA | 提供上下文相关的帮助 |
| 3.3.6 错误预防(所有) | AAA | 所有输入均可撤销、检查或确认 |
| 3.3.7 冗余输入 ⭐ 2.2 新增 | A | 同一会话中无需再次输入之前已输入的信息 |
| 3.3.8 无障碍身份验证(最低要求) ⭐ 2.2 新增 | AA | 身份验证无需认知功能测试或记忆要求 |
| 3.3.9 无障碍身份验证(增强) ⭐ 2.2 新增 | AAA | 为认知功能测试提供增强的替代方案 |
原则 4:稳健
指南 4.1 — 兼容
| 标准 | 等级 | 规则 |
|---|---|---|
| 在 WCAG 2.2 中已移除 — 因 HTML5 错误修正机制而过时 | ||
| 4.1.2 名称、角色、值 | A | 所有组件都有无障碍名称、角色和状态/值 |
| 4.1.3 状态消息 | AA | 向屏幕阅读器播报状态消息,且不更改焦点 |
第 5 部分 — 实现参考
5.1 颜色对比度规则
| 场景 | AA 最低要求 | AAA 增强要求 |
|---|---|---|
| 普通文本(<18px 或 <14px 粗体) | 4.5:1 | 7:1 |
| 大号文本(≥18px 或 ≥14px 粗体) | 3:1 | 4.5:1 |
| UI 组件和图形 | 3:1 | 3:1 |
| 占位符文本 | 4.5:1 | — |
| 禁用元素 | 豁免 | 豁免 |
| 装饰性元素 | 豁免 | 豁免 |
深色模式说明: 深色主题仍必须满足对比度要求。避免使用纯黑色 #000000
(会造成光晕效应/眼疲劳);改用 #0f0f0f 或 #121212 等柔和的黑色。
深色背景上的高饱和度颜色可能会产生视觉抖动 — 请降低饱和度。
工具: WebAIM Contrast Checker、Colour Contrast Analyser、axe DevTools、Chrome DevTools。
5.2 语义化 HTML — 优先使用原生元素
始终优先使用语义化 HTML,而不是 ARIA。ARIA 是补充手段,不能替代语义化 HTML。
<header>…</header>
<nav aria-label="Main navigation">…</nav>
<main>
<article>…</article>
<aside>…</aside>
</main>
<footer>…</footer>
<div class="header">…</div>
<div class="nav">…</div>
语义化元素 → 地标映射:
| 元素 | ARIA 地标角色 |
|---|---|
<header> | banner |
<nav> | navigation |
<main> | main |
<aside> | complementary |
<footer> | contentinfo |
<section aria-label="..."> | region |
<form> | form |
标题层级规则: 切勿跳过层级。<h1> → <h2> → <h3>(不要使用 h1 → h3)。
可以使用多个 <h1> 元素,但第一个应是页面/章节标题。
5.3 WAI-ARIA 速查参考
仅在语义化 HTML 不足时使用 ARIA。ARIA 首要规则:如果可以使用内置语义的原生 HTML 元素或属性,就不要使用 ARIA。
地标角色
<div role="banner">…</div>
<div role="navigation">…</div>
<div role="main">…</div>
<div role="complementary">…</div>
<div role="contentinfo">…</div>
<div role="search">…</div>
<div role="region" aria-label="…">…</div>
小部件角色(交互组件)
role="button"
role="checkbox"
role="radio"
role="combobox"
role="listbox"
role="option"
role="menu"
role="menuitem"
role="tab"
role="tablist"
role="tabpanel"
role="dialog"
role="alertdialog"
role="slider"
role="progressbar"
role="tooltip"
role="tree"
role="treeitem"
role="grid"
role="gridcell"
实时区域角色(动态内容)
role="alert"
role="status"
role="log"
role="timer"
aria-live="polite"
aria-live="assertive"
aria-live="off"
aria-atomic="true"
aria-relevant="additions text"
必要的 ARIA 属性
aria-label="Close dialog"
aria-labelledby="heading-id"
aria-describedby="description-id help-text-id"
aria-expanded="true|false"
aria-selected="true|false"
aria-checked="true|false|mixed"
aria-pressed="true|false|mixed"
aria-disabled="true"
aria-hidden="true"
aria-invalid="true|false|grammar|spelling"
aria-busy="true|false"
aria-controls="panel-id"
aria-owns="list-id"
aria-haspopup="true|menu|listbox|tree|grid|dialog"
aria-autocomplete="none|inline|list|both"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="50"
aria-valuetext="50 percent"
aria-current="page|step|location|date|time|true|false"
5.4 键盘导航实现
每个交互组件都必须完全支持键盘操作。
焦点管理规则
/* NEVER do this without a custom replacement */
*:focus { outline: none; }
/* ✅ Custom focus indicator — meets WCAG 2.4.7 and 2.4.13 */
:focus-visible {
outline: 2px solid #005fcc;
outline-offset: 2px;
border-radius: 2px;
}
/* Different styles for mouse vs keyboard */
:focus:not(:focus-visible) { outline: none; }
:focus-visible { outline: 2px solid #005fcc; outline-offset: 2px; }
Tab 顺序
<div role="button" tabindex="0">Custom Button</div>
<button tabindex="3">Bad</button>
<div tabindex="-1" id="modal-content">…</div>
跳过导航链接
<a href="#main-content" class="skip-link">Skip to main content</a>
<style>
.skip-link {
position: absolute;
top: -100%;
left: 0;
padding: 0.5rem 1rem;
background: #000;
color: #fff;
z-index: 9999;
transition: top 0.1s;
}
.skip-link:focus { top: 0; }
</style>
<main id="main-content">…</main>
按组件划分的键盘操作模式
| 组件 | 按键 | 操作 |
|---|---|---|
| 所有组件 | Tab | 将焦点向前移动 |
| 所有组件 | Shift+Tab | 将焦点向后移动 |
| 按钮 | Enter / Space | 激活 |
| 链接 | Enter | 跳转到链接 |
| 复选框 | Space | 切换状态 |
| 单选按钮组 | 方向键 | 选择选项 |
| 选择框/下拉菜单 | 方向键 | 浏览;按 Enter 进行选择 |
| 对话框 | Escape | 关闭 |
| 对话框 | Tab | 仅在对话框内循环切换 |
| 菜单 | 方向键 | 浏览菜单项 |
| 菜单 | Escape | 关闭 |
| 菜单 | Enter / Space | 激活菜单项 |
| 滑块 | 方向键 | 增大/减小 |
| 滑块 | Home / End | 最小值 / 最大值 |
| 标签页 | 方向键 | 切换标签页 |
| 树形控件 | 方向键 | 展开/折叠/导航 |
模态框的焦点陷阱
// Trap focus within modal while open
function trapFocus(modal) {
const focusable = modal.querySelectorAll(
'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])'
);
const firstEl = focusable[0];
const lastEl = focusable[focusable.length - 1];
modal.addEventListener('keydown', (e) => {
if (e.key !== 'Tab') return;
if (e.shiftKey) {
if (document.activeElement === firstEl) {
e.preventDefault(); lastEl.focus();
}
} else {
if (document.activeElement === lastEl) {
e.preventDefault(); firstEl.focus();
}
}
});
}
5.5 无障碍组件模式
模态框 / 对话框
<dialog
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
aria-describedby="modal-desc"
id="my-modal"
>
<h2 id="modal-title">Confirm Action</h2>
<p id="modal-desc">Are you sure you want to delete this item?</p>
<button type="button" autofocus>Cancel</button>
<button type="button">Delete</button>
</dialog>
// HTML5 <dialog> — built-in focus management + Escape handling
const modal = document.getElementById('my-modal');
const trigger = document.getElementById('open-btn');
trigger.addEventListener('click', () => modal.showModal());
modal.addEventListener('close', () => trigger.focus()); // Restore focus
无障碍按钮(自定义)
<button type="button" aria-pressed="false" id="dark-toggle">
<svg aria-hidden="true" focusable="false">…</svg>
Dark Mode
</button>
<button type="button" aria-label="Close dialog">
<svg aria-hidden="true" focusable="false">…</svg>
</button>
手风琴
<div class="accordion">
<h3>
<button
type="button"
aria-expanded="false"
aria-controls="panel-1"
id="btn-1"
>Section Title</button>
</h3>
<div id="panel-1" role="region" aria-labelledby="btn-1" hidden>
Panel content here
</div>
</div>
标签页
<div role="tablist" aria-label="Settings sections">
<button role="tab" aria-selected="true" aria-controls="panel-a" id="tab-a" tabindex="0">General</button>
<button role="tab" aria-selected="false" aria-controls="panel-b" id="tab-b" tabindex="-1">Privacy</button>
</div>
<div role="tabpanel" id="panel-a" aria-labelledby="tab-a">…</div>
<div role="tabpanel" id="panel-b" aria-labelledby="tab-b" hidden>…</div>
组合框 / 自动补全
<label for="city-input">City</label>
<input
type="text"
id="city-input"
role="combobox"
aria-expanded="false"
aria-haspopup="listbox"
aria-autocomplete="list"
aria-controls="city-list"
/>
<ul id="city-list" role="listbox" aria-label="Cities">
<li role="option" id="opt-1">New York</li>
<li role="option" id="opt-2">Los Angeles</li>
</ul>
提醒 / 状态消息
<div role="status" aria-live="polite" aria-atomic="true">
Form saved successfully.
</div>
<div role="alert" aria-live="assertive" aria-atomic="true">
Error: Please enter a valid email address.
</div>
5.6 表单 — 无障碍模式
<div class="field">
<label for="email">
Email address
<span aria-hidden="true" class="required">*</span>
<span class="sr-only">(required)</span>
</label>
<input
type="email"
id="email"
name="email"
autocomplete="email"
aria-required="true"
aria-describedby="email-hint email-error"
aria-invalid="false"
/>
<p id="email-hint" class="hint">We'll never share your email.</p>
<p id="email-error" class="error" role="alert" hidden>
Please enter a valid email address.
</p>
</div>
<fieldset>
<legend>Notification preferences</legend>
<label><input type="radio" name="notif" value="email"> Email</label>
<label><input type="radio" name="notif" value="sms"> SMS</label>
<label><input type="radio" name="notif" value="none"> None</label>
</fieldset>
表单验证规则:
- 在
blur或submit时进行验证,而不是每次击键时都验证 - 使用
aria-invalid="true"标记无效字段 - 通过
aria-describedby将错误消息关联到字段 - 对动态插入的错误消息使用
role="alert" - 切勿仅使用占位符文本作为标签
- 为常见字段提供 autocomplete 属性
5.7 图片和替代文本
<img src="chart.png" alt="Sales by region 2024: North 35%, South 28%, East 22%, West 15%">
<img src="divider.png" alt="">
<button aria-label="Search">
<img src="magnifier.svg" alt="">
</button>
<figure>
<img src="architecture.png" alt="System architecture diagram" aria-describedby="arch-desc">
<figcaption id="arch-desc">
Three-tier architecture: client tier connects to API gateway,
which routes to microservices cluster, backed by PostgreSQL and Redis.
</figcaption>
</figure>
<div class="hero-bg" role="img" aria-label="Abstract blue wave">…</div>
替代文本指南:
- 保持简洁(通常不超过 125 个字符;复杂内容最多 250 个字符)
- 不要以“图片”或“图像”开头
- 描述用途和内容,而不只是外观
- 对于图表:包含关键数据点
- 对于徽标:仅使用公司名称
5.8 排版无障碍性
/* ✅ Accessible base typography */
html {
font-size: 100%; /* Don't fix to px — respects user browser setting */
}
body {
font-size: 1rem; /* = 16px by default */
line-height: 1.5; /* Readable line spacing */
max-width: 75ch; /* Limit line length for readability */
font-family: /* Choose a readable, designed typeface */;
}
/* ✅ Allow text spacing override (WCAG 1.4.12) */
* {
line-height: inherit !important; /* only in text-spacing tests */
letter-spacing: inherit !important;
word-spacing: inherit !important;
}
/* ✅ Respect zoom — use relative units */
h1 { font-size: clamp(1.5rem, 4vw, 2.5rem); }
p { font-size: clamp(1rem, 1.2vw, 1.125rem); }
| 指南 | 最低要求 | 建议值 |
|---|---|---|
| 正文文字大小 | 14px(0.875rem) | 16px(1rem) |
| 行高 | 1.4 | 1.5–1.6 |
| 最大行长 | — | 65–75 个字符 |
| 字母间距 | — | 0 至 +0.05em |
| 段落间距 | — | 字号的 1.5 倍 |
无障碍字体选择:
- 选择字形区分清晰的字体(能够区分 b/d/p/q)
- 正文避免使用装饰性字体
- 小字号时避免使用较细字重(< 300)
- 确保 x-height 足够高,以提高可读性
5.9 动效和动画
/* ✅ Respect prefers-reduced-motion — implement always */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* ✅ Progressive enhancement pattern */
.card { /* base — no motion */ }
@media (prefers-reduced-motion: no-preference) {
.card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
}
动效规则:
- 切勿自动播放带有动态效果的视频 — 使用
prefers-reduced-motion - 闪烁内容:每秒最多闪烁 3 次(WCAG 2.3.1)
- 视差效果、整页滚动劫持 → 提供
prefers-reduced-motion: reduce替代方案 - 加载动画可以接受;如果没有替代方案,则不应使用整页动画过渡
5.10 响应式和移动端无障碍性
/* ✅ WCAG 1.4.10 Reflow — no horizontal scroll at 320px */
@media (max-width: 320px) {
.container { width: 100%; padding: 0 1rem; }
table { display: block; overflow-x: auto; }
}
/* ✅ WCAG 2.5.8 Minimum target size — 24×24px with spacing */
button, a, [role="button"] {
min-height: 44px; /* Meet WCAG 2.5.5 AAA for comfort */
min-width: 44px;
padding: 0.5rem 1rem;
}
/* ✅ Ensure focus remains visible at all sizes */
:focus-visible {
outline: 2px solid currentColor;
outline-offset: 3px;
}
移动端注意事项:
- 使用适当的
type属性:type="email"、type="tel"、type="number" - 不要禁用缩放:
<meta name="viewport" content="width=device-width, initial-scale=1">— 切勿添加user-scalable=no - 使用 VoiceOver(iOS)和 TalkBack(Android)进行测试
- 避免仅通过悬停触发的交互(触摸设备不支持悬停)
- 对数字、小数、电话、电子邮件、网址、搜索输入使用
inputmode属性
5.11 表格
<table>
<caption>Q4 2024 Sales by Region</caption>
<thead>
<tr>
<th scope="col">Region</th>
<th scope="col">Sales (USD)</th>
<th scope="col">Growth</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">North America</th>
<td>$1,240,000</td>
<td>+12%</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row">Total</th>
<td>$3,890,000</td>
<td>+8%</td>
</tr>
</tfoot>
</table>
规则:
- 使用
<caption>为每个表格添加标题 - 使用
<th scope="col|row|colgroup|rowgroup">标记所有表头单元格 - 使用
<thead>、<tbody>、<tfoot>构建结构 - 不要用表格进行布局;改用 CSS Grid/Flexbox
第 6 部分 — 以无障碍性为导向的设计:前端美学指南
6.1 面向无障碍界面的设计思维
编码之前,先确立一个大胆、有意为之的美学方向,并将无障碍纳入规划:
- 目的:要解决什么问题?谁会使用它?用户可能有哪些残障?
- 基调:选择一种极致的美学风格:极简主义、极繁字体风、自然风、编辑风、粗野主义、复古未来主义、奢华精致风、趣味风、工业风等。
- 约束:框架、性能、WCAG 级别(目标为 AA)、对比度要求
- 差异化:是什么让它令人难忘且无障碍?大胆的设计与 WCAG 并不冲突。
无障碍能为设计提供指引,而不是限制设计。优秀的无障碍设计会将以下要素融入设计:
- 将强对比度作为设计特色,而非负担
- 将清晰的字体层级作为视觉系统
- 将醒目的焦点状态作为有意设计的元素
- 将语义结构作为构图逻辑
6.2 无障碍色彩系统设计
设计无障碍色彩系统:
:root {
/* Accessible palette using HSL for easier contrast calculation */
--color-bg: hsl(220, 15%, 10%); /* Near-black background */
--color-surface: hsl(220, 12%, 15%); /* Elevated surface */
--color-border: hsl(220, 10%, 25%); /* Subtle border */
--color-text-primary: hsl(220, 10%, 95%); /* High contrast text: >7:1 */
--color-text-secondary: hsl(220, 8%, 70%); /* Medium contrast: ~4.5:1 */
--color-text-muted: hsl(220, 6%, 55%); /* Low contrast: ~3:1 (large text only) */
--color-accent: hsl(210, 100%, 65%); /* Action color: verify contrast */
--color-accent-hover: hsl(210, 100%, 72%); /* Hover state */
--color-error: hsl(0, 80%, 65%); /* Error — verify 4.5:1 on bg */
--color-success: hsl(145, 60%, 55%); /* Success — verify 4.5:1 on bg */
--color-warning: hsl(45, 90%, 55%); /* Warning — verify 4.5:1 on bg */
/* Focus ring — must contrast 3:1 with adjacent colors (WCAG 2.4.7) */
--color-focus: hsl(210, 100%, 65%);
--focus-ring: 0 0 0 3px var(--color-focus);
}
/* ✅ Color-scheme support */
@media (prefers-color-scheme: light) {
:root {
--color-bg: hsl(0, 0%, 99%);
--color-text-primary: hsl(220, 20%, 8%);
/* ... recalculate all contrasts */
}
}
多模态传达(绝不只依赖颜色):
/* ❌ Color only for error */
.input-error { border-color: red; }
/* ✅ Color + icon + text + pattern */
.input-error {
border-color: var(--color-error);
border-width: 2px;
background-image: url("error-icon.svg");
background-position: right 12px center;
background-repeat: no-repeat;
padding-right: 40px;
}
6.3 字体排版设计系统
/* ✅ Accessible, distinctive type scale */
:root {
/* Scale: Major Third (1.25) or Perfect Fourth (1.333) */
--text-xs: 0.75rem; /* 12px — captions, labels */
--text-sm: 0.875rem; /* 14px — secondary text */
--text-base: 1rem; /* 16px — body */
--text-lg: 1.125rem; /* 18px — lead text */
--text-xl: 1.25rem; /* 20px */
--text-2xl: 1.5rem; /* 24px */
--text-3xl: 1.875rem; /* 30px */
--text-4xl: 2.25rem; /* 36px */
--text-5xl: 3rem; /* 48px */
--text-6xl: 3.75rem; /* 60px */
/* Accessible line heights */
--leading-tight: 1.25;
--leading-snug: 1.375;
--leading-normal: 1.5;
--leading-relaxed:1.625;
--leading-loose: 2;
/* Weights */
--font-light: 300; /* Use sparingly, large sizes only */
--font-normal: 400;
--font-medium: 500;
--font-semibold: 600;
--font-bold: 700;
--font-black: 900;
}
选择兼具独特性与无障碍性的字体:
- 避免将 Inter、Roboto、Arial、system-ui 用于展示文字——这些字体过于普通
- 选择光学尺寸范围较大的字体:DM Sans、Fraunces、Bricolage Grotesque、 Playfair Display、Syne、General Sans、Cabinet Grotesk、Instrument Serif
- 将独特的展示字体与简洁易读的正文字体搭配使用
- 确认所选字体在正文字号(14–18px)下清晰易读
- 始终准备一组易读的后备字体
6.4 将焦点指示器作为设计元素
将焦点指示器设计为有意为之的设计特色,而不是事后补上的元素:
/* Option 1: Solid outline */
:focus-visible {
outline: 3px solid var(--color-accent);
outline-offset: 3px;
border-radius: var(--radius-sm, 4px);
}
/* Option 2: Box shadow (inset for contained elements) */
:focus-visible {
outline: none;
box-shadow: 0 0 0 3px var(--color-bg), 0 0 0 5px var(--color-accent);
}
/* Option 3: High visibility for text links */
a:focus-visible {
outline: none;
background-color: var(--color-accent);
color: var(--color-bg);
text-decoration: none;
border-radius: 2px;
padding: 0 2px;
}
/* Verify contrast: focused vs unfocused → must be ≥3:1 change */
6.5 面向操作无障碍的间距与布局
/* ✅ Generous interactive targets */
.btn {
/* Minimum visually 44×44 (WCAG 2.5.5 AAA) */
min-height: 44px;
padding-inline: 1.25rem;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
/* ✅ Inline links — guaranteed minimum height */
a {
display: inline-block; /* Ensures tap area */
padding: 0.25em 0;
}
/* ✅ Spacing between small targets (WCAG 2.5.8 AA) */
.icon-group { gap: 0.5rem; } /* 8px between 24px targets = sufficient */
/* ✅ Grid and flex for logical order */
/* Visual order must match DOM order for keyboard/screen reader users */
/* Avoid using CSS `order` property to reorder visual layout */
6.6 无障碍动画设计
/* ✅ Always implement reduced-motion alternative */
:root {
--duration-fast: 150ms;
--duration-base: 250ms;
--duration-slow: 400ms;
--easing-out: cubic-bezier(0.16, 1, 0.3, 1);
}
@media (prefers-reduced-motion: reduce) {
:root {
--duration-fast: 0ms;
--duration-base: 0ms;
--duration-slow: 0ms;
}
}
/* ✅ Purpose-driven micro-interactions */
.btn {
transition:
background-color var(--duration-fast) ease,
transform var(--duration-fast) var(--easing-out),
box-shadow var(--duration-fast) ease;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.btn:active {
transform: translateY(0);
box-shadow: none;
}
第 7 部分 — 测试清单
快速检查(5 分钟)
- 使用 Tab 键遍历整个页面——能否访问并激活所有内容?
- 焦点指示器是否始终可见?
- 使用浏览器 DevTools 检查颜色对比度
- 运行 axe DevTools 或 Lighthouse 无障碍审计
- 检查禁用 CSS 后的页面(是否能看到语义结构?)
标准检查(WCAG 2.2 AA)
可感知
- 所有图像都有有意义的替代文本(装饰性图像使用
alt="") - 颜色对比度:普通文本 ≥4.5:1,大号文本和 UI 元素 ≥3:1
- 不只依赖颜色传递信息
- 在 200% 缩放下,内容仍可读且功能正常
- 页面宽度为 320px 时无水平滚动(内容可重排)
- 覆盖文本间距不会破坏布局
- 不播放无控制项的自动播放音频
可操作
- 所有功能都可通过键盘访问
- 无键盘陷阱
- 焦点指示器始终可见
- 焦点不会被粘性页眉或页脚遮挡
- 提供跳过导航链接
- 所有页面都有描述性的
<title> - 焦点顺序符合逻辑
- 链接文本清晰且具有描述性
- 触控目标至少为 24×24px,且间距充足
- 内容不会以每秒超过 3 次的频率闪烁
易于理解
- 正确设置
<html lang="en"> - 使用
lang属性标记语言变更 - 焦点变化不会意外改变上下文
- 各页面的导航保持一致
- 所有表单输入框都有可见标签
- 错误得到识别和说明,并提供解决建议
- 帮助机制位于一致的位置
稳健
- 所有表单控件都通过 HTML 或 ARIA 声明名称、角色和值
- 状态消息无需改变焦点即可播报
- HTML 有效且结构规范(无嵌套错误)
- 正确使用 ARIA 角色和状态
- 动态内容更新会被播报
屏幕阅读器测试
使用以下组合测试:NVDA + Chrome(Windows)、VoiceOver + Safari(Mac)、TalkBack + Chrome(Android)
- 标题结构便于导航且逻辑清晰
- 地标便于导航(nav、main、aside、footer)
- 表单标签能够正确朗读
- 字段无效时会播报错误消息
- 会播报动态内容(警报、状态)
- 模态框:限制焦点、按 Escape 关闭、焦点返回原处
第 8 部分 — 法律与标准背景
| 标准 | 司法管辖区 | 依据 |
|---|---|---|
| ADA(《美国残疾人法案》) | 美国 | WCAG 2.1 AA |
| Section 508 | 美国(联邦) | WCAG 2.0 AA |
| EN 301 549 | 欧盟 | WCAG 2.1 AA |
| AODA | 加拿大安大略省 | WCAG 2.0 AA |
| Equality Act 2010 | 英国 | WCAG 2.1 AA |
| BS 8878 | 英国 | WCAG 2.1 |
| BITV 2.0 | 德国 | WCAG 2.1 AA |
建议目标:WCAG 2.2 AA — 超过所有主要法律的要求,并能适应未来发展。
第 9 部分 — 工具与资源
测试工具
| 工具 | 类型 | 用途 |
|---|---|---|
| axe DevTools | 浏览器扩展 + CI | 最全面的自动化检查 |
| WAVE(WebAIM) | 浏览器扩展 | 可视化错误叠加层 |
| Lighthouse | Chrome DevTools | 快速基线审计 |
| Colour Contrast Analyser | 桌面应用 | 像素级精准对比度检查 |
| WebAIM Contrast Checker | Web 工具 | 快速计算对比度 |
| NVDA | 屏幕阅读器 | Windows 屏幕阅读器测试(免费) |
| VoiceOver | 屏幕阅读器 | Mac/iOS 内置 |
| TalkBack | 屏幕阅读器 | Android 内置 |
| Keyboard | 手动 | 始终测试仅使用键盘的导航 |
重要参考资料
- WCAG 2.2 规范:https://www.w3.org/TR/WCAG22/
- WAI-ARIA 编写实践指南:https://www.w3.org/WAI/ARIA/apg/
- WebAIM 文章:https://webaim.org/articles/
- The A11Y Project:https://www.a11yproject.com/
- Deque University:https://dequeuniversity.com/
- MDN 无障碍指南:https://developer.mozilla.org/en-US/docs/Web/Accessibility
- Inclusive Components(Heydon Pickering):https://inclusive-components.design/
- A11y Coffee:https://a11y.coffee/
总结:不可妥协的 AA 规则
构建任何前端界面时,始终必须遵守以下 WCAG 2.2 AA 规则:
- 对比度:普通文本为 4.5:1,大号文本和 UI 组件为 3:1
- 替代文本:每张信息性图片都要有描述性替代文本;装饰性图片使用
alt="" - 键盘:100% 的功能都可仅通过键盘访问和操作
- 焦点可见:焦点指示器必须清晰可见,且绝不能移除
- 标签:每个表单输入项都必须关联一个可见的
<label> - 错误:以文本指出错误,不能仅依靠颜色;使用 aria-invalid;提供建议
- 标题:层级逻辑清晰,不跳过级别
- 语言:始终设置
<html lang="..."> - 语义化 HTML:使用
<button>、<a>、<nav>、<main>等,不要用<div>替代 - 信息不能仅靠颜色传达:绝不能只用颜色传达任何信息
- 重排:内容在 320px 宽度下仍可使用,无需水平滚动
- 触控目标:交互元素至少为 24×24px,或提供充足间距
- 焦点不被遮挡:粘性页眉/页脚不能遮挡获得焦点的元素
- 状态消息:向屏幕阅读器播报动态状态/错误消息
- 帮助一致:各页面上的帮助机制应位于相同位置