真正切实有效的四项工程实践
立场明确,并非面面俱到。遵循这些模式,可在代码评审前消除大多数无障碍回归问题。
语义化 HTML 优先,ARIA 其次
凡是原生 HTML 能胜任的场景,优先使用原生元素。<button>
内置了键盘交互、焦点、role 以及 Enter/Space 激活行为;<label>
将控件与其描述绑定;<dialog> 自带焦点限制和页面其余部分置为
inert 的行为,否则需手动实现;<details> 无需任何
JavaScript 即可实现折叠展开。ARIA 是逃生舱——在没有合适原生元素时有用,
叠加在已能正常工作的元素上则有害。参考:
WCAG 2.2 成功准则和
W3C ARIA 创作实践指南。
键盘支持不是一个复选框
每个交互元素都必须仅凭键盘即可操作。Tab 和 Shift+Tab 用于移动焦点;Enter
或 Space 用于激活;Esc 用于关闭临时界面(模态框、气泡框、菜单)。焦点指示器必须
可见——绝不能在没有替代方案的情况下使用 outline: none;。焦点必须
合理移动——模态框打开时焦点应移入其内;模态框关闭时焦点应返回触发它的元素。
务必在用鼠标测试之前先用键盘测试;
鼠标会隐藏键盘会立即暴露的缺陷。
无障碍名称与描述
重点不在于随处添加 aria-label。无障碍名称默认来自元素的文本内容;
aria-labelledby 引用另一节点的文本;aria-label
以硬编码字符串覆盖一切(应作为最后手段,因为它绕过翻译且易与可见标签产生偏差)。
无障碍描述是独立的——aria-describedby 引用帮助文本或错误消息节点。
请通过 DevTools 无障碍树验证屏幕阅读器实际接收到的内容——假设与现实往往不符。
在真实 CI 环境中测试,而非仅在本地
本地 axe 检查只是健全性测试,绿色 CI 才是回归门控。将
eslint-plugin-jsx-a11y 接入每个 PR;在单元测试和端到端测试中
添加 axe-core 断言;在代表性页面运行 AT-driver 流程,让真实屏幕阅读器
参与验证。屏幕阅读器测试工具综述
详细介绍了哪些值得自动化、哪些仍需人工评审。
工具链——13 款精选工具与库
每个条目对应一个生命周期阶段——lint、单元测试、端到端、运行时、监测或人工评审——以便将合适的工具接入合适的门控环节。
-
运行时
axe-core
驱动以下大多数工具的无障碍测试引擎。可嵌入单元测试、端到端测试套件,或在开发时挂载至 DOM 进行运行时检查。
-
E2E
axe DevTools
具备 Playwright、Cypress、Jest 和 Selenium 集成的浏览器扩展,是基于 axe-core 的标准开发者界面工具。
-
针对 React JSX 的静态分析 lint 规则。在代码评审前捕捉常见问题(缺失 alt、无效 role、div 上绑定 onClick 等)。
-
运行时
vue-axe
Vue 运行时无障碍检查器——在开发过程中将 axe-core 发现的违规项输出至浏览器控制台。
-
以无障碍为优先的单元测试查询匹配器,鼓励开发者按屏幕阅读器的方式(通过 role 和无障碍名称)而非按 class 定位节点。
-
具备一流 axe-core 集成的端到端浏览器自动化工具,可在 CI 中跨真实浏览器运行无障碍断言。
github.com/dequelabs/axe-core-npm/tree/develop/packages/playwright ↗
-
单元测试
Storybook a11y addon
在设计系统内对每个组件进行 axe-core 扫描,在违规项进入产品代码前将其拦截。
-
监测
Pa11y
封装了 axe-core 和 HTML CodeSniffer 的 CLI 扫描器,适合用作在回归时使构建失败的 CI 门控原语。
-
由 Google 驱动,内置无障碍审计(底层使用 axe-core),并附带性能和最佳实践评分,适用于趋势追踪。
-
人工
WebAIM WAVE
将问题直接叠加显示在实时页面上的可视化扫描器,适合不希望阅读 JSON 报告的设计师和内容作者。
-
IDE 内即时反馈循环——在测试光标旁运行 axe-core 断言,加快开发者接入新组件时的迭代速度。
-
W3C 孵化的标准,用于通过自动化测试驱动真实屏幕阅读器——无障碍自动化测试的前沿,终于超越了 axe 式静态检查。
-
用户实际使用的屏幕阅读器。对于自动化工具无法捕捉的 30%~40% 的 WCAG 问题,任何自动化手段都无法替代人工屏幕阅读器评审。
框架专项指南
不同生态系统中无障碍问题的差异化表现——以及各框架的深度覆盖内容。
React
列表的 key 属性(key 设置不当会在元素重排时混淆屏幕阅读器)。路由切换时的焦点管理
(缺少此处理,焦点会停留在触发导航的链接上,新页面对辅助技术用户而言不可见)。
Portal 与焦点限制——渲染至 document.body 的模态框仍需将焦点保持在自身内部。
SSR 与客户端之间导致 DOM 结构变化的水合不匹配,可能悄无声息地破坏 ARIA 关系。
关于
现代框架中的 aria-live 实时区域
的深度解析,涵盖了 React 协调机制容易破坏的实时区域播报模式。
Vue / Svelte / Solid
存在类似模式;响应式状态变更默认不触发实时区域更新。每个框架的响应式模型对"DOM
已变更"有不同的定义,屏幕阅读器感知到——或感知不到——节点更新的方式存在微妙差异。
Vue 的 v-if 与 v-show、Svelte 的响应式声明,以及
Solid 的细粒度响应性,对于外观相同的代码会产生不同的无障碍树更新。
原生移动端(iOS + Android)
完全不同的 API 层。iOS 通过 UIAccessibility(以及 SwiftUI 的
.accessibilityLabel()/.accessibilityHint())
向 VoiceOver 暴露信息;Android 通过 AccessibilityNodeInfo 向 TalkBack 暴露信息。
Web 风格的 ARIA 无法直接迁移。
原生移动端无障碍 API
一文将 Web 概念映射至对应的原生实现,帮助有 Web 背景的工程师不再凭空猜测。
组件库
无头库(Radix UI、Headless UI、React Aria)处理了最复杂的部分——焦点管理、键盘交互、 ARIA 接线——并将视觉设计完全留给开发者。全功能库(Material UI、Chakra、Ant)自带 视觉风格,但无障碍质量参差不齐,"默认无障碍"几乎不等于"经过真实屏幕阅读器审计"。 无障碍组件库综述 依据真实辅助技术测试结果对主流选项进行了评级。
无障碍 PR 评审清单
可打印、可粘贴至 PR 模板,或接入机器人。每位评审者至少应检查的最低标准。
- 交互元素仅凭键盘即可操作(Tab + Enter + Space + Esc)。
- 每个交互元素的焦点指示器可见。
- 表单输入项具有关联的
<label>。 - 错误消息与对应输入项关联(
aria-describedby或相邻文本)。 - 动态内容变更在适当时通过
aria-live播报。 - 模态对话框限制焦点,并在关闭时将焦点归还至触发元素。
- 图像具有有意义的替代文本;装饰性图像使用
alt=""。 - 列表使用
<ul>/<ol>/<dl>,而非<div>堆砌。 - 标题层级合理(无跳级)。
- Lint 与 axe 测试在合并前通过 CI。
常见工程反模式
通过代码评审、在生产环境中才暴露问题的模式——应尽早识别。
- "覆盖层组件"——声称为现有站点增加无障碍性的第三方 JavaScript。 它并不能实现这一点,反而频繁破坏辅助技术层,自身已引发了一波诉讼。背景信息: 覆盖层供应商。
-
role="button"加在<div>上 ——仅添加了 role 播报,却缺少键盘行为(Enter、Space)和焦点参与。请使用<button>。 - 在可聚焦元素上使用
aria-hidden="true"——造成焦点陷阱:键盘用户可以 Tab 到该元素,屏幕阅读器却被告知忽略它。 请用tabindex="-1"将元素移出 Tab 顺序,或去掉aria-hidden。 - 无键盘处理的自定义下拉框——基于
<div>的组合框点击可打开,却忽略方向键、Home/End、预输入和 Esc。 无障碍组件库综述 介绍了开箱即用地正确处理此场景的库。 - 不播报的 Toast 通知——临时界面在渲染时未使用
role="status"(礼貌)或role="alert"(强制)。 视力正常的用户能看到,屏幕阅读器用户则无法感知。 - 破坏无障碍树的生成式 DOM——input 外包裹的重量级封装
(由十二层嵌套
<div>构建的自定义 select)通常会对辅助技术 隐藏实际控件。应测试辅助技术所见,而非仅测试用户所见。
工具包与监测流程
大多数团队按顺序需要三件事:出现问题时进行一次性排查扫描;需要理解底层模式时查阅 工程参考资料;无障碍进入生产路线图后建立持续监测层。 免费 WCAG 2.2 扫描器满足第一项需求——粘贴一个公开 URL,即可在新标签页获得 axe 驱动的报告。文章专栏的工程内容 满足第二项——包括将无障碍债务视为技术债务的分析,以及针对规模化无障碍管理团队的 无障碍事故 SRE 事后复盘。
对于持续监测层, 监测工具选型指南 是本站最具判断力的内容。该指南对 axe Monitor、Siteimprove、Level Access 和 Qualibooth 进行了评级——其中 Qualibooth 将监测与人工审计交接相结合,填补了纯自动化技术栈的 缺失环节——评估维度涵盖覆盖广度、误报率,以及数据接入工程工作流的顺畅程度。 监测的根本目的,是确保今天交付的修复不会在下个迭代周期中回归。
工程师常见问题
每次无障碍启动会议都会出现的问题,已同步至 FAQPage 结构化数据。
aria-label比可见文本标签更好吗?-
不。可见文本几乎总是更优的选择——它惠及视力正常的用户、有认知障碍的视力正常用户、
语音控制用户(他们说出所见内容)以及翻译工具。仅当没有可见文本可供关联(纯图标按钮)
或可见文本确实不是期望的无障碍名称时,才应使用
aria-label。 - 应该对所有元素使用 ARIA role 吗?
-
不应该。ARIA 第一条规则是 "do not use ARIA"。原生 HTML 元素内置了正确的
role、正确的键盘行为以及正确的无障碍树语义。仅当没有合适的原生元素时才使用
ARIA——例如选项卡列表、树形结构,或无法使用
<dialog>的自定义对话框。 - 如何在 CI 中测试无障碍性?
-
三个层次,按成本排序。(1)Lint:每个 PR 运行
eslint-plugin-jsx-a11y。 (2)单元测试:每个组件使用@testing-library/jest-dom加jest-axe(或@axe-core/react)。(3)端到端:在代表性 用户旅程上运行@axe-core/playwright。在预发布环境添加 Pa11y 或 Lighthouse CI 门控,以捕捉低层次漏掉的偏差。 - axe-core 和 Lighthouse 是同一个东西吗?
- Lighthouse 在底层使用 axe-core 进行无障碍审计,但它运行的是规则子集,并将结果 呈现在更宏观的性能/SEO/最佳实践报告中。axe-core 本身是引擎——需要完整规则集或 扩展规则时,应直接使用 axe-core。对大多数团队而言:用 Lighthouse 进行趋势追踪, 用 axe-core 作为实际门控。
- React 项目的最低无障碍测试配置是什么?
-
在现有 ESLint 配置中添加
eslint-plugin-jsx-a11y(create-react-app 和 Next.js 默认已包含)。在单元测试配置中添加@testing-library/jest-dom和jest-axe,并在每个组件至少一个测试中调用expect(await axe(container)).toHaveNoViolations()。 这是最低标准——仅需数小时配置即可捕捉约 40% 的真实 WCAG 问题。 - 需要用真实屏幕阅读器进行测试吗?
- 需要,对于辅助技术用户实际会使用的任何产品功能都应如此。自动化工具能捕捉结构性 缺陷(缺失标签、颜色对比度、role 不匹配),但无法捕捉体验性问题(焦点跳转至页脚、 实时区域未播报、"无障碍"模态框将焦点困在错误子树中)。建议每次重大发版时至少用 Windows 上的 NVDA 和 macOS/iOS 上的 VoiceOver 进行一次人工评审。
后续步骤
如需排查特定页面,可通过免费 WCAG 2.2 扫描器快速检测。 在将 AT-driver 接入 CI 之前,请先阅读 屏幕阅读器测试工具综述。 若无障碍工作正从"一次性审计"升级为"持续生产关注点", 监测工具选型指南 是本站在供应商选型方面最具实操价值的内容。