微型博客的用户群里,有人用屏幕阅读器刷时间线,有人只用键盘完成发帖,有人把字号放大到 200%,也有人在颠簸的公交车上单手操作。无障碍(Accessibility,简称 a11y)不是「给少数人做的慈善功能」,而是「让产品在更多环境下可用」的工程约束。它同时有法律强制力:欧盟《欧洲无障碍法案》(EAA)自 2025 年起要求面向消费者的数字服务达到 WCAG 2.1 AA,美国 ADA 相关判例也在持续增加。本文讲透这套体系:合规基线、语义化与 ARIA、键盘与焦点、屏幕阅读器、视觉与动效、移动端触控、测试工具链,以及包容性设计的产品视角。
前置:移动端适配与响应式布局 、国际化与全球化运营 、富文本渲染与安全清洗 。
目录
- 1. 为什么必须做:法律、用户与商业三重驱动
- 2. WCAG 2.2 四原则与合规等级
- 3. 语义化 HTML 与 ARIA 五规则
- 4. 键盘可达性与焦点管理
- 5. 屏幕阅读器适配与动态播报
- 6. 视觉无障碍:对比度、色盲与缩放
- 7. 动效、认知与移动端触控
- 8. 测试工具链:自动化与人工
- 9. 包容性设计的产品视角
- 10. 速查表与一句话记忆
- 延伸阅读
1. 为什么必须做:法律、用户与商业三重驱动
无障碍常被当成「上线前的补丁」,但它其实是三条硬约束的交汇点。
法律驱动:
□ 欧盟 EAA(2025 生效):面向消费者的数字服务须达 WCAG 2.1 AA
□ 美国 ADA / Section 508:政府与公共采购强约束
□ 中国《信息无障碍 信息技术互联网内容无障碍可访问性技术要求》
□ 后果:罚款、诉讼、应用商店下架、政府采购资格丧失
用户驱动:
□ 全球约 16% 人口有某种形式的显著残障(WHO)
□ 视障、低视力、色盲、运动障碍、听障、认知障碍
□ 临时性障碍:强光下看不清、单手操作、手臂受伤
□ 情境性障碍:嘈杂环境、慢网络、旧设备
商业驱动:
□ 可访问的页面天然对爬虫与搜索引擎友好(语义化收益)
□ 更清晰的层级与焦点管理同时提升普通用户体验
□ 覆盖更广的设备与网络条件 → 转化率提升
关键认知:无障碍的受益面远大于残障用户。给视频加字幕的人可能只是在地铁里没戴耳机;给按钮加足够大的触控目标,同时救了误触的用户。所以它不是「特例处理」,而是「把默认值设对」。
2. WCAG 2.2 四原则与合规等级
WCAG(Web Content Accessibility Guidelines)是国际公认的基线,用四原则(POUR)组织所有准则。
P — 可感知(Perceivable):
信息必须能被感知到
· 非文本内容有替代文本(图片 alt、图标 aria-label)
· 视频有字幕、音频有文字稿
· 内容不依赖单一感官(不能只靠颜色传达信息)
O — 可操作(Operable):
界面组件必须可操作
· 全部功能键盘可达
· 用户能控制时限、暂停动效
· 有足够时间阅读,不诱发癫痫(闪烁 < 3Hz)
U — 可理解(Understandable):
信息与操作必须可理解
· 页面语言声明(lang)
· 导航与组件行为可预测
· 输入错误有明确提示与纠正建议
R — 健壮(Robust):
必须能被各类辅助技术解析
· 有效、完整的 HTML 语义
· 名称/角色/值(Name, Role, Value)对辅助技术可见
合规等级(每级包含前一级):
| 等级 | 含义 | 典型要求 |
|---|---|---|
| A | 最低要求 | 图片有 alt、键盘可达、语言声明 |
| AA | 法律通用目标 | 对比度 ≥ 4.5:1、字幕、焦点可见 |
| AAA | 增强 | 对比度 ≥ 7:1、手语翻译、无时限 |
目标定在 AA 是行业共识:A 太弱(大量用户仍不可用),AAA 成本陡增(部分准则对内容形态有冲突)。微型博客的验收基线应是「全部 A + AA 关键项」,并把 AA 写进 CI 门禁。
3. 语义化 HTML 与 ARIA 五规则
无障碍的第一原则是「优先用原生元素」,而不是「用 div + ARIA 硬造」。
ARIA 五规则(W3C 官方):
1. 能用原生 HTML 语义,就别用 ARIA
· <button> 而不是 <div role="button">
· 原生元素自带键盘、焦点、状态,ARIA 只描述不实现
2. 不要改变原生语义
· <h1 role="button"> 会让标题变成按钮 → 混乱
3. 所有可交互的 ARIA 组件必须键盘可达
· role="button" 必须能 Tab 到、能 Enter/Space 触发
4. 不要给可聚焦元素加 aria-hidden="true"
· 焦点落进隐藏元素 → 屏幕阅读器「消失」
5. 所有 ARIA 元素必须有可访问名称(accessible name)
· role="button" 必须配文本或 aria-label
常见对照:
| 需求 | 错误做法 | 正确做法 |
|---|---|---|
| 按钮 | <div onclick> | <button> |
| 导航区 | <div class="nav"> | <nav> |
| 切换开关 | <span> 变色 | <input type="checkbox" role="switch"> |
| 模态框 | 隐藏的 div | <dialog> + showModal() |
| 图标按钮 | 只有图标 | <button aria-label="点赞"> |
可访问名称(Accessible Name) 的计算优先级:aria-labelledby → aria-label → 原生文本内容 → title。图标按钮必须显式给名称,否则屏幕阅读器只会念「按钮」,用户根本不知道点了会发生什么。
<!-- 好:原生按钮 + 明确的名称与状态 -->
<button type="button" aria-pressed="false" aria-label="点赞,当前 128 个赞">
<svg aria-hidden="true">...</svg>
</button>
<!-- 列表语义:帖子流用 ul/li,而不是一堆 div -->
<ul role="feed" aria-busy="false">
<li aria-posinset="1" aria-setsize="-1">...</li>
</ul>
aria-live 是动态内容的关键:点赞数、通知红点这类「无刷新变化」需要主动播报,否则屏幕阅读器用户完全感知不到。
4. 键盘可达性与焦点管理
只用键盘的用户,唯一的信息通道是「焦点」。焦点管理做错,整个页面就是黑箱。
键盘可达性检查清单:
□ Tab 顺序与视觉顺序一致(不要用 tabindex="1,2,3" 打乱)
□ 每个可交互元素都有可见的焦点样式(:focus-visible)
□ 焦点不会被「困」在某个区域(modal 除外,那是刻意陷阱)
□ 有「跳到主内容」链接(Skip to content)
□ 不劫持浏览器快捷键(Ctrl/Cmd + 组合键)
□ Esc 关闭弹层,关闭后焦点回到触发元素
焦点陷阱(Focus Trap)是模态框的必备:打开对话框后,Tab 必须在对话框内部循环,不能跑到背后的页面。
// 打开模态框:记录触发元素 → 移焦到对话框 → 限制 Tab 循环
const trigger = document.activeElement;
dialog.showModal();
dialog.querySelector('[autofocus]')?.focus();
// 关闭:恢复焦点,避免用户「丢失位置」
dialog.addEventListener('close', () => trigger?.focus());
| 场景 | 焦点应落到哪里 |
|---|---|
| 页面加载 | 无自动聚焦(避免打乱) |
| 打开模态 | 对话框内第一个可聚焦元素 |
| 关闭模态 | 触发它的那个按钮 |
| 客户端路由跳转 | 新的主标题(h1),并播报页面变化 |
| 表单校验失败 | 第一个出错的字段 |
单页应用(SPA)的路由切换是重灾区:切换视图后焦点往往留在原地或丢失,屏幕阅读器用户以为「什么都没发生」。做法是路由切换后把焦点移到新页面的 h1(tabindex="-1")并用 aria-live 播报标题。
5. 屏幕阅读器适配与动态播报
屏幕阅读器(Screen Reader)把界面翻译成语音或盲文,是视障用户的主入口。
主流屏幕阅读器:
□ NVDA(Windows,免费,最常用)
□ JAWS(Windows,商业,企业常用)
□ VoiceOver(macOS / iOS,内置)
□ TalkBack(Android,内置)
□ Orca(Linux)
朗读模型:
角色(Role)→ 名称(Name)→ 状态(State)→ 值(Value)
例:「按钮,点赞,已按下,128」
任何一项缺失,用户就少了判断依据
文本替代(alt)的写法:
| 图片类型 | 写法 | 示例 |
|---|---|---|
| 信息图 | 描述内容与含义 | alt="2026 年日活增长曲线,Q2 环比 +18%" |
| 装饰图 | 留空 alt | alt=""(不是省略,省略会念文件名) |
| 功能图 | 描述功能 | alt="搜索" |
动态播报用 live region,礼貌程度分两档:
<!-- 状态提示:不打断当前朗读,等空档播报 -->
<div aria-live="polite" class="sr-only">已点赞,共 129 个赞</div>
<!-- 紧急提示:立即打断(慎用,会打断用户阅读) -->
<div aria-live="assertive" role="alert">发布失败,请重试</div>
aria-live="polite" 适合点赞、关注、通知数变化;assertive/role="alert" 只留给错误与危险操作。切忌滥用 assertive——它会把用户正在读的内容打断,反而制造混乱。
6. 视觉无障碍:对比度、色盲与缩放
视觉无障碍的量化基线是 WCAG 的对比度要求。
对比度要求(AA):
□ 正文文本:≥ 4.5:1
□ 大号文本(≥ 18.66px 粗体 或 ≥ 24px):≥ 3:1
□ 图标、边框、图形等非文本:≥ 3:1
□ 焦点指示器:≥ 3:1(相对相邻颜色)
测量:用相对亮度公式计算,别靠肉眼猜
L = 0.2126R + 0.7152G + 0.0722B(先做 sRGB 线性化)
对比度 = (L1 + 0.05) / (L2 + 0.05)
色盲(约 8% 男性、0.5% 女性)是另一条硬约束:
不能只靠颜色传达信息:
□ 错误提示:红色 + 图标 + 文字,而非仅变红
□ 状态区分:色块 + 形状/文字标签
□ 图表:用色 + 图案/直接标注数值
□ 链接:下划线或加粗,而非仅靠颜色
字号缩放与文本重排:
- 浏览器缩放 200% 时,内容不能横向滚动、不能重叠(WCAG 1.4.10 Reflow)。
- 用相对单位(
rem/em)而非固定px,尊重用户的浏览器字号设置。 - 支持系统级「放大文本」与「暗色模式」,别写死颜色。
/* 尊重系统偏好,而不是覆盖它 */
:root { color-scheme: light dark; }
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
@media (prefers-contrast: more) {
:root { --border: #000; --text-muted: #333; }
}
7. 动效、认知与移动端触控
除了视觉与听觉,还有两条常被忽略的维度:动效与触控。
动效无障碍:
□ 尊重 prefers-reduced-motion,关闭视差与自动播放
□ 自动播放的视频默认静音 + 可暂停(WCAG 1.4.2)
□ 闪烁频率 < 3Hz,避免光敏性癫痫
□ 动画不作为唯一的反馈手段
认知无障碍:
□ 语言简单、句子短、避免行话
□ 表单标签明确、错误信息给出「怎么改」
□ 一致的导航与命名,降低记忆负担
移动端触控无障碍:
□ 触控目标 ≥ 44×44 px(iOS HIG)/ 48×48 dp(Material)
□ 目标间距足够,避免误触相邻按钮
□ 手势必须有单点替代(滑动删除 → 也提供按钮)
移动端的目标尺寸是最容易漏的一条:设计稿上「小巧精致」的图标按钮,实际手指根本点不准。工程上把最小点击区写进设计令牌(Design Token),让 min-height: 44px 成为组件默认值。
8. 测试工具链:自动化与人工
无障碍测试必须「自动化兜住 30~40%,人工覆盖其余」,两者缺一不可。
自动化能查(约 30~40% 的问题):
□ 缺失 alt / aria-label
□ 对比度不足
□ 表单缺 label
□ 无效的 ARIA 角色
□ 标题层级跳跃(h1 → h3)
自动化查不出(必须人工):
□ 焦点顺序是否合理
□ alt 文本是否有意义(「图片」也是合法 alt,但没价值)
□ 屏幕阅读器体验是否连贯
工具矩阵:
| 层次 | 工具 | 用途 |
|---|---|---|
| 静态 | eslint-plugin-jsx-a11y | 开发期拦截常见错误 |
| 组件 | jest-axe / vitest-axe | 组件测试断言无违规 |
| E2E | @axe-core/playwright | 页面级扫描 |
| 审计 | Lighthouse / axe DevTools | 人工排查与报告 |
| 人工 | NVDA / VoiceOver / 仅键盘 | 真实体验验证 |
// CI 中的组件级断言:违规即失败
import { axe } from 'jest-axe';
test('发帖编辑器无无障碍违规', async () => {
const { container } = render(<Composer />);
const results = await axe(container, {
rules: { 'color-contrast': { enabled: true } },
});
expect(results).toHaveNoViolations();
});
人工测试的最低要求:用键盘完整走一遍「注册 → 发帖 → 点赞 → 关注 → 退出」,再用 VoiceOver 或 NVDA 听一遍首页。这两条 15 分钟的手工检查,能抓住自动化完全看不到的问题。
9. 包容性设计的产品视角
无障碍(a11y)解决「能不能用」,包容性设计(Inclusive Design)解决「为谁设计」。
包容性设计的三个延伸:
□ 设备包容:低端机、慢网络、小存储也能用
· 首屏体积预算、图片渐进加载、离线降级
□ 语言包容:多语言、RTL 布局、本地化日期与数字
□ 能力包容:不同阅读水平、不同文化背景、不同输入方式
产品落地时的取舍原则:
- 把无障碍写进 Definition of Done,而不是单独排期一个「无障碍专项」。
- 设计系统里内置对比度校验、焦点样式、触控尺寸,让「正确」成为默认。
- 用真实用户测试,而非只跑工具——找一位屏幕阅读器用户做一次可用性测试,胜过一百次自动扫描。
- 把无障碍指标(如 axe 违规数)纳入质量看板,让它和性能、错误率一样被追踪。
工程要点:无障碍不是「加功能」,而是「不破坏」。用原生语义、给足对比度、保住键盘与焦点、播报动态变化,这四件事覆盖了 80% 的价值。剩下的靠工具链兜底与人工验证补齐。
10. 速查表与一句话记忆
| 问题 | 一句话答案 |
|---|---|
| 合规基线定哪级 | WCAG 2.2 AA(A 全部 + AA 关键项) |
| 语义化怎么做 | 优先原生元素,ARIA 只描述不实现 |
| 图标按钮怎么标 | 必须有可访问名称(aria-label) |
| 焦点怎么管 | 模态陷阱、关闭还原、路由后移到标题 |
| 动态变化怎么播 | aria-live=“polite”,错误才用 assertive |
| 对比度多少 | 正文 4.5:1、大字/图形 3:1 |
| 颜色能用吗 | 不能只靠颜色传达信息 |
| 动效怎么处理 | 尊重 prefers-reduced-motion,闪烁 < 3Hz |
| 触控目标多大 | ≥ 44×44 px |
| 测试怎么做 | 自动化 30% + 键盘/屏幕阅读器人工验证 |
一句话记忆:无障碍 = WCAG 2.2 AA 基线(POUR 四原则)+ 原生语义优先(ARIA 五规则)+ 键盘可达与焦点管理(陷阱/还原/跳转链接)+ 屏幕阅读器适配(名称/角色/值 + live region 播报)+ 对比度 4.5:1 与不依赖颜色 + 尊重减少动效偏好 + 触控目标 44px + 自动化扫描与人工验证双轨——把「正确」设成默认值,让产品在更多人和更多环境下可用。
延伸阅读
- 移动端适配与响应式布局
- 国际化与全球化运营
- 富文本渲染与安全清洗
- 前端无障碍实战 — 语义、ARIA 与焦点管理
- 无障碍测试方法 — 自动化与人工结合的验证流程
- 产品设计方法论
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。