构建可访问的Web应用:超越复选框合规
大多数团队将可访问性视为税务审计。这是一件不愉快的年度任务,通过运行自动化扫描器并修复其标记的任何问题来解决。结果是,从技术上讲,软件通过了检查清单,但对于这些清单旨在保护的人群来说,仍然根本无法使用。
可访问的Web应用开发不是一项QA任务。它是一门工程学科——需要与性能优化或安全加固相同的严谨性、设计思维和架构承诺。不同之处在于,当您做错安全时,您的数据会泄露;当您做错可访问性时,真实的人会被完全排除在您的产品之外。
复选框合规与真正可用性之间的差距是巨大的,弥合这一差距需要的不仅仅是一个插件或CI步骤。它需要我们思考Web方式的转变。
可访问性是一个工程问题
框架很重要。当可访问性被视为合规义务——法律要求工程部门处理的事情时,结果是可预测的:肤浅地检查颜色对比度,在图片上添加一些alt文本,然后提交一份报告。当它被视为工程约束,就像响应式设计或国际化一样,它会从根本上塑造架构。
考虑团队处理性能的方式有何不同。没有人等到冲刺结束才”添加性能”。团队选择高效的数据结构、延迟加载资源,并在整个开发过程中分析渲染周期。可访问性应得到同样的对待。您第一天选择的DOM结构决定了六个月后屏幕阅读器是否能够解析您的界面。
在这方面做得很好的组织——GOV.UK、BBC、Shopify——将可访问性嵌入到他们的组件库和设计系统中。它不是一个功能。它是系统的一个属性。
WCAG 2.2:实际变化内容
WCAG 2.2于2023年底最终确定,引入了九项新的成功标准。其中三项值得前端工程师特别关注:
- 2.4.11 焦点不被遮挡(最低)— AA: 当元素接收键盘焦点时,不能被作者创建的内容(如粘性标题或横幅cookie)完全隐藏。使用固定定位导航 surprisingly很容易违反这一点。
- 2.5.7 拖动操作 — AA: 任何使用拖动的功能也必须提供单指针替代方案。您的看板卡片需要非拖动方式来移动卡片。
- 2.5.8 目标尺寸(最低)— AA: 交互式目标必须至少为24×24 CSS像素,内联链接和浏览器控制尺寸的元素除外。这一点比团队预期的会捕获更多组件。
完整规范内容密集,但这三个标准反映了一个更广泛的转变:WCAG越来越关注运动和认知可访问性,而不仅仅是视觉障碍。您的设计需要考虑那些无法执行精确操作或拖动手势的用户。
语义HTML:您可能忽略的基础
每次可访问性对话都应从语义HTML开始,因为它解决的问题比大多数开发者意识到的要多。一个正确结构的文档——使用<nav>、<main>、<article>、<aside>、逻辑顺序的标题——为辅助技术提供了您页面的完整地图,无需一行ARIA代码。
<!-- 不良结构:屏幕阅读器看到的是扁平的div列表 -->
<div class="nav">...</div>
<div class="content">
<div class="title">仪表板</div>
<div class="card">...</div>
</div>
<!-- 正确结构:地标和标题层次结构 -->
<nav aria-label="主导航">...</nav>
<main>
<h1>仪表板</h1>
<section aria-labelledby="stats-heading">
<h2 id="stats-heading">使用统计</h2>
...
</section>
</main>
第二个例子不仅对屏幕阅读器更好。它对SEO更好,可读性更好,未来维护性也更好。原生HTML元素具有隐含的ARIA角色——<nav>自动具有role="navigation",<button>自动具有role="button"和内置键盘处理。首先应使用这些元素。
ARIA:修复工具,而非功能
正如官方规范所述,ARIA的第一条规则是:如果可以使用原生HTML元素,就不要使用ARIA。ARIA的存在是为了弥补HTML的不足之处——自定义小部件、动态内容区域、复杂的交互模式。它不是为了掩盖<div>的混乱。
当需要使用 ARIA 时,请精确使用:
<!-- 自定义下拉菜单:这里使用 ARIA 是合理的 -->
<div role="combobox"
aria-expanded="false"
aria-haspopup="listbox"
aria-controls="options-list"
aria-label="选择一个国家">
<input type="text" aria-autocomplete="list" />
</div>
<ul id="options-list" role="listbox" aria-label="国家">
<li role="option" aria-selected="false">加拿大</li>
<li role="option" aria-selected="true">德国</li>
</ul>
ARIA 的危险在于错误使用会主动损害可访问性。一个没有键盘事件处理程序的 <div role="button"> 比普通的 <div> 更糟糕,因为它向辅助技术表明该元素是可交互的,但实际上并非如此。ARIA 向可访问性树做出了承诺。您的代码必须遵守这些承诺。
键盘导航:被忽视的交互模式
如果您的应用程序无法完全通过键盘操作,那么它就不具备可访问性。就这么简单。键盘导航不是边缘情况——它是屏幕阅读器用户、许多行动障碍用户以及大量仅仅更喜欢键盘操作的高级用户的主要交互模式。
需要正确实现的关键模式:
| 模式 | 预期行为 |
|---|---|
| Tab / Shift+Tab | 在逻辑顺序的交互元素之间移动 |
| Enter / 空格 | 激活按钮、链接和控件 |
| 方向键 | 在复合组件内导航(选项卡、菜单、单选按钮组) |
| Escape | 关闭模态框、下拉菜单和覆盖层;将焦点返回到触发器 |
| Home / End | 跳转到列表或菜单中的第一个/最后一个项目 |
焦点管理是大多数应用程序崩溃的地方。当模态框打开时,焦点必须移入其中。当它关闭时,焦点必须返回到触发它的元素。当内容动态加载时,焦点不应不可预测地跳转。这些是工程要求,而不是可有可无的功能。
// 模态对话框的焦点陷阱
function trapFocus(modalElement) {
const focusable = modalElement.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
modalElement.addEventListener('keydown', (e) => {
if (e.key !== 'Tab') return;
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
});
first.focus();
}
颜色对比度和视觉设计
WCAG AA 要求普通文本的对比度至少为 4.5:1,大文本(18px 粗体或 24px 常规)的对比度为 3:1。这些是最低要求。在可能的情况下应该追求更高的对比度——特别是对于正文文本,因为在各种屏幕质量下的可读性很重要。
自动化工具经常忽略的常见失败:
- 图像上的文本: 如果文本覆盖在照片上,那么针对纯色背景测量的对比度就没有意义。使用半透明背景或确保文本下方的图像区域有足够的对比度。
- 禁用状态: 具有 2:1 对比度的灰色按钮在技术上可以豁免于 WCAG,但它们仍然是一个可用性问题。使禁用状态不仅仅通过颜色来区分——使用图案、边框或图标。
- 焦点指示器: 默认的浏览器焦点环很丑陋,所以团队会移除它。然后他们忘记添加替代品。可见的焦点指示器对键盘用户是强制性的。可以设置它的样式,但永远不要移除它。
- 仅通过颜色传达的信息: 仅使用颜色的红/绿状态指示器、仅使用颜色的必填字段标记——这些对大约 8% 的色觉缺陷男性用户来说都是失败的。始终将颜色与文本、图标或图案结合使用。
屏幕阅读器测试:NVDA 和 VoiceOver
自动化工具能捕捉到大约 30-40% 的可访问性问题。其余的则需要使用实际的辅助技术进行手动测试。两种屏幕阅读器应该是每个前端开发者测试工作流程的一部分:
NVDA(Windows,免费)与 Firefox 或 Chrome 配对,代表了屏幕阅读器用户的大部分群体。VoiceOver(macOS/iOS,内置)对于测试 Safari 和移动可访问性至关重要。它们的行为在重要方面有所不同——它们如何宣布 ARIA 实时区域,如何处理动态内容,如何导航表格。
一个实用的测试流程:
- 仅使用 Tab 键浏览整个页面。你能到达每个交互式元素吗?顺序是否合理?
- 使用屏幕阅读器的标题导航(NVDA:
H键;VoiceOver:VO+Command+H)在标题之间跳转。层次结构是否有意义? - 激活模态框或动态内容更改。更新是否被宣布?焦点是否正确移动?
- 从头到尾完成表单。标签是否被宣布?错误消息是否与其字段关联?
- 在移动设备上测试。iOS 上的 VoiceOver 与滑动手势会揭示与桌面测试完全不同的问题。
这不是可选测试。如果你从未使用屏幕阅读器来导航自己的应用程序,你就不知道它是否有效。
自动化测试:axe 和 Lighthouse
自动化测试能够发现低垂的果实——缺失的替代文本、损坏的标签关联、对比度不足。该领域有两个主导工具:
axe-core 由 Deque 开发,是大多数可访问性测试集成的引擎。它可以在浏览器中(通过 axe DevTools 扩展)、CI 中(通过 @axe-core/cli 或 jest-axe)以及作为端到端测试套件的一部分运行。
// 与 Jest 和 React Testing Library 的集成
import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
test('LoginForm 没有可访问性违规', async () => {
const { container } = render(<LoginForm />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
Lighthouse 包含一个与 axe 部分重叠的可访问性审计。它对于快速检查很有用,但不应该成为你唯一的工具——其评分系统可能会给人一种虚假的完整感。Lighthouse 可访问性得分为 100 并不意味着你的应用程序是可访问的。这意味着它通过了 Lighthouse 运行的特定自动化检查。
理想的设置:在你的组件测试套件中使用 axe 来及早发现回归问题,加上对每个涉及 UI 的功能分支进行手动屏幕阅读器测试。
React 和 Vue:框架特定的陷阱
基于组件的框架引入了静态 HTML 中不存在可访问性问题。
React 陷阱:
- 片段(
<>...</>)如果组件组合不当可能会破坏标题层级。一个总是渲染<h3>的Card组件,当放置在期望<h4>的部分中时,会产生不正确的结构。 - 客户端路由不会宣布页面更改。使用 React Router 时,您需要手动管理焦点,并使用活动区域或焦点管理向屏幕阅读器宣布导航。
- 门户(用于模态框和工具提示)会破坏 DOM 顺序。必须明确维护焦点管理和 ARIA 关系。
Vue 陷阱:
- 过渡组件在动画期间可能会使屏幕阅读器处于不确定状态。确保在进入/离开过渡期间正确切换
aria-hidden。 v-html会绕过模板编译,并可能注入无法访问的标记。对任何动态 HTML 进行清理和验证,以确保结构正确。- 动态组件加载(
<component :is="...">)在组件切换时可能导致焦点丢失。在动态更新后跟踪和恢复焦点。
表单可访问性:大多数应用程序失败的地方
表单是大多数应用程序中风险最高的可访问性界面。用户正在提交数据、进行购买、创建账户。屏幕阅读器无法解析的表单不仅不方便,而且是核心功能的障碍。
<!-- 可访问的表单模式 -->
<form aria-labelledby="form-title" novalidate>
<h2 id="form-title">创建账户</h2>
<div>
<label for="email">电子邮件地址 <span aria-hidden="true">*</span></label>
<input id="email" type="email" required
aria-required="true"
aria-describedby="email-hint email-error"
aria-invalid="false" />
<p id="email-hint">我们将发送确认链接。</p>
<p id="email-error" role="alert" hidden>
请输入有效的电子邮件地址。
</p>
</div>
</form>
关键细节:每个输入都需要一个通过 for/id 链接的可见 <label>。错误消息必须使用 aria-describedby 与其字段以编程方式关联。必填字段需要 aria-required="true",而不仅仅是视觉上的星号。验证错误应使用 aria-invalid 和 role="alert" 来确保它们被立即宣布。
占位符文本不是标签。它在输入时消失,默认情况下对比度不足,并且并非所有屏幕阅读器都能可靠地宣布。最多将其用作补充提示文本。
超越合规性的商业案例
合规性理由显而易见:诉讼成本高昂。近年来,美国与ADA相关的网络无障碍诉讼案件已超过4,000起,而欧洲《无障碍法案》将于2025年6月起实施强制性要求。但将无障碍仅视为合规问题是过于简化的。它将无障碍定位为风险缓解而非价值创造。
更有力的理由:
- 市场规模:全球有超过十亿人生活在某种形式的残疾中。仅在美国,残障人士群体就控制着超过4,900亿美元的可支配收入。一个无障碍产品会将这些客户排除在外。
- SEO重叠:语义化HTML、适当的标题层级、描述性链接文本和图片替代文本都是无障碍要求,它们能直接提升搜索引擎索引效果。
- 移动情境使用:无障碍改进惠及每个人。字幕帮助嘈杂环境中的用户。高对比度帮助阳光直射环境中的用户。键盘导航帮助触摸板损坏的用户。路缘效应是真实存在的。
- 代码质量:无障碍代码就是结构良好的代码。认真对待无障碍的团队能生产出更易维护、文档更完善、更可测试的组件。构建无障碍界面所需的纪律性提高了整体质量标准。
前进之路
无障碍网络应用开发并非追求完美。它是对一种实践的承诺——就像安全是一种实践,而不是你一旦达成就可以忘记的状态。从语义化HTML开始。为你的CI流水线添加自动化测试。花三十分钟用屏幕阅读器体验自己的产品。修复你发现的问题。重复这个过程。
通过自动化审计与构建盲人、行动障碍者或认知障碍用户实际能够使用的产品之间存在着巨大差距。缩小这一差距是工程工作。困难、细致、重要的工程工作。在我们的行业中,这项工作早已姗姗来迟。
