guides
当错误提示把用户拒之门外
无障碍错误提示不只是加几个 ARIA 属性那么简单。本文讲解要让错误提示对所有人可用,究竟需要做什么。
表单出错的那一刻
想象这样的场景:一位盲人用户填完结账表单,点击提交,屏幕阅读器却没有播报任何有用的信息。没有提示,没有解释。页面已经用红色在视觉上标出了五个验证错误,但用户对此一无所知。他再次点击提交。又一次。最终,他离开了。
这并不是什么罕见场景。不可访问的错误处理是我们在真实审计中发现的最常见障碍之一,而且它往往在最糟糕的时刻让用户受挫——正当他们试图完成交易或提交重要内容的时候。
好消息是,修复它在很大程度上只是一个”知道什么才算好”的问题。失败的模式几乎总是那几种。
为什么错误提示对辅助技术用户如此困难
视力正常的鼠标用户往往能立刻注意到验证错误——红色边框、警告图标、出现在输入框下方的文字。但依赖屏幕阅读器、键盘导航或语音控制的用户,面对的是一种根本不同的体验。
当表单提交后出现错误时:
- 屏幕阅读器用户可能什么都听不到,除非焦点移动到错误处,或有实时区域进行播报
- 键盘用户需要能够通过键盘触达每条错误提示,并理解它属于哪个字段
- 语音控制用户需要通过可见且准确的标签来定位字段——如果标签错误或缺失,他们就会卡住
这一切都不会自动发生。你必须自己构建。
适用的 WCAG 标准
WCAG 2.2 中有四条成功标准直接涵盖错误处理:
3.3.1 错误识别(A 级)——如果自动检测到错误,必须以文本形式向用户标识并描述出错的项目。不能只通过颜色来传达错误。
3.3.2 标签或说明(A 级)——要求特定格式的输入项,必须在输入之前或输入过程中说明该要求,而不是等到提交失败之后。
3.3.3 错误建议(AA 级)——如果检测到错误且已知修正建议,就应当提供。“日期无效”是不够的,“请按 DD/MM/YYYY 格式输入日期”才可以。
3.3.4 错误预防(AA 级)——对于后果重大的提交(金融交易、法律协议),必须让用户在操作不可撤销之前,有机会检查、修正并确认。
此外还有 4.1.3 状态消息(AA 级),它涵盖不接收焦点的动态注入消息——这些消息必须通过 role="status" 或 role="alert" 由辅助技术播报。
一条无障碍错误提示到底需要什么
要把错误提示做好,需要关注四个不同的层面:
1. 程序化关联
每条错误提示都必须通过 aria-describedby 与其字段绑定。这会告诉屏幕阅读器在字段获得焦点时播报错误文本。
<!-- Before: error text exists visually but is invisible to screen readers -->
<label for="email">Email</label>
<input type="email" id="email" class="error">
<p class="error-text">Enter a valid email address.</p>
<!-- After: programmatic association via aria-describedby -->
<label for="email">Email address</label>
<input
type="email"
id="email"
aria-describedby="email-err"
aria-invalid="true"
>
<p id="email-err" class="error-text">Enter a valid email address.</p>
aria-invalid="true" 属性表明该字段当前无效,大多数屏幕阅读器会在焦点落到该字段时播报这一状态。
2. 提交后的焦点管理
当表单提交后存在错误时,焦点必须移动到一个有用的位置。最常见的两种模式是:
- 将焦点移动到表单顶部的错误摘要,其中列出所有错误并链接到对应字段
- 将焦点移动到第一个出错的字段
如果焦点停留在提交按钮上,或者根本没有移动,屏幕阅读器用户就只能自己在表单中逐项摸索错误——前提是他们知道有错误存在。
// After validation fails, move focus to the error summary
const summary = document.getElementById('error-summary');
summary.setAttribute('tabindex', '-1');
summary.focus();
3. 动态错误的实时区域播报
对于在用户输入过程中出现的错误(内联实时验证),请使用 ARIA 实时区域,让消息无需移动焦点即可被播报。
<p role="alert" aria-live="assertive" id="password-err">
Password must be at least 8 characters.
</p>
对于需要立即处理的错误,使用 role="alert" / aria-live="assertive";对于可以等用户停顿后再播报的次要反馈,使用 role="status" / aria-live="polite"。
4. 错误提示本身
措辞很重要。无障碍的错误提示应当是:
- 具体的:说明哪里出了问题,而不只是说出了问题
- 可操作的:明确告诉用户该怎么做才能修复
- 礼貌的:避免”你输入了无效值”之类的指责性语言
| 不可访问 | 可访问 |
|---|---|
| ”输入无效" | "电子邮件地址必须包含 @ 符号,例如 name@example.com" |
| "错误" | "日期必须为 DD/MM/YYYY 格式" |
| "必填字段" | "请填写全名" |
| "密码无效" | "密码必须至少 8 个字符,并包含一个数字” |
不同错误场景的模式
内联字段错误
最常见的模式。错误提示直接显示在对应字段的下方。适用于简单表单。需要 aria-describedby 和 aria-invalid 才能被正确播报。
错误摘要横幅
对于存在多个错误的长表单,在顶部放置一个摘要,以链接形式列出所有问题并跳转到对应字段。提交失败后将焦点移动到这里。这是 GOV.UK 采用的模式,也被广泛推荐用于复杂表单。
Toast 通知
出现在屏幕边缘、短暂存在的消息,是常见模式中无障碍性最差的一类。它们在屏幕阅读器用户触达之前就消失了,往往根本不会被播报,也不提供任何恢复路径。避免用 Toast 来展示错误。如果一定要使用,请让表单本身同时保持一个持久的错误状态。
系统错误
当服务器端出现问题时,请用通俗的语言告诉用户发生了什么,以及接下来可以做什么。只说”出了点问题”而不给任何进一步指引,会让用户束手无策。
<div role="alert" aria-live="assertive">
<p>We couldn't save your changes due to a network error.
Please check your connection and try again.</p>
</div>
测试核对清单
实现错误处理之后,请手动验证以下各项——自动化工具无法捕捉其中的大部分:
- 提交一个空的必填表单后,焦点会移动到错误摘要或第一个出错的字段
- 每条错误提示都通过
aria-describedby与其字段关联 - 出错的字段带有
aria-invalid="true" - 焦点落到受影响的字段时,错误提示会被播报
- 实时验证使用了实时区域
- 错误提示使用具体、可操作的措辞——而不只是”无效”
- 颜色不是标示错误的唯一方式(红色之外还有图标或文字标签)
- 可以通过键盘 Tab 键导航到错误处
- 提交后的焦点顺序合乎逻辑,不会不可预测地跳转
- Toast 消息配有持久的错误状态
为什么自动化工具漏掉了其中大部分
自动化扫描工具可以检查 aria-describedby 是否指向一个存在的元素,以及 aria-invalid 是否存在。但它无法检查提交后焦点是否正确移动、错误文本是否准确,或者 Toast 通知是否在被读到之前就消失了。我们的人工无障碍审计包含使用真实辅助技术进行的表单专项测试——这是端到端验证错误处理是否真正有效的唯一可靠方式。
如果你不确定自己的表单处于什么水平,可以先运行免费扫描找出可自动检测的问题,再逐步深入。