aria-describedby 不自动播报错误,需配合 aria-invalid="true" 和 aria-errormessage 或 aria-live 才能触发语义化播报;错误容器须预置唯一id、添加 aria-live="polite" 与 role="status"、用 textcontent 更新内容,并确保 id 与属性值严格一致。

aria-describedby 本身不播报错误,别指望它自动喊出“错了”
很多人加了 aria-describedby="email-error" 就以为屏幕阅读器会主动读错,结果一试静悄悄。根本原因:aria-describedby 只是建立「静态引用关系」,它不携带语义状态——不管里面写的是“请输入邮箱”还是“邮箱格式错误”,读屏器都平铺直叙地念,不会加重语气、不会中断当前播报,更不会标记为需紧急处理的问题。
真正触发错误语义播报的,是 aria-invalid="true" 配合 aria-errormessage(推荐)或 aria-live(兼容兜底)。aria-describedby 在这里只干一件事:把输入框和那段错误文字“连上线”,让焦点落在输入框上时,能顺带读出来。
- 必须同时设置
aria-invalid="true",否则多数读屏器(尤其旧版 NVDA/JAWS)直接忽略该描述 - 优先用
aria-errormessage="xxx"而非aria-describedby指向错误元素——这是 WCAG 2.1+ 明确推荐的错误关联方式 -
aria-describedby仍可保留,用来挂格式提示(如email-hint),形成“提示 + 错误”双通道
错误元素 ID 必须预置、唯一、稳定,不能 JS 动态生成后就扔
常见失败场景:表单提交后 JS 创建 <div id="email-error">域名不合法</div> 并插入 DOM,但屏幕阅读器还是不读。问题不在 aria-live,而在 ID 的生命周期管理。
动态插入的错误元素,如果 ID 是随机生成(比如 error-uuid4()),或者在组件销毁时没清理,会导致后续校验找不到目标、ID 冲突、甚至 aria-describedby 值残留无效引用。
- 服务端渲染或初始 HTML 中就该预置空错误容器:
<p id="email-error" class="sr-only" aria-live="polite"></p> - 客户端 JS 更新时,只改
textContent,不删重建;ID 固定为error-${fieldName}这类可预测格式 - React/Vue 列表中,确保每个表单项的 ID 前缀含唯一 key,避免重复(如
error-user-0-email) - ID 大小写、连字符、下划线必须与
aria-describedby或aria-errormessage值完全一致,浏览器区分大小写
错误容器得“活”起来:aria-live + role="status" 是实际生效的关键
即使 ID 对上了、aria-invalid 也设了,错误文本更新后仍不播报,大概率是可访问性树没感知到变化。单纯用 innerHTML = errorMsg 或 textContent 写入,对某些读屏器(尤其是 iOS VoiceOver + Safari)不够“响亮”。
必须给错误容器本身加实时播报能力。最稳妥的做法是组合使用:
- 错误容器加
aria-live="polite"和role="status"(后者是旧浏览器降级必需) - 写入前先清空:
errorEl.textContent = '',再赋新值,避免连续快速更新触发节流或跳过 - 禁用
display: none或visibility: hidden隐藏错误容器——它们会让元素退出可访问性树;改用opacity: 0; position: absolute; pointer-events: none; - 错误容器建议放在对应
<input>后紧邻位置,DOM 顺序靠近有助于视觉定位和读屏器逻辑
服务端返回的错误字段名千奇百怪,前端别硬编码 error.message
后端 JSON 错误结构五花八门:{ "errors": { "email": "格式错误" } }、{ "detail": "邮箱不能为空" }、{ "message": "Validation failed" }……直接取 error.message 渲染,大概率拿到空值或乱码。
更糟的是,用 innerHTML 注入服务端返回的内容,等于敞开 XSS 大门。而无障碍播报又要求内容必须实时、准确、安全。
- 永远用
textContent设置错误文本,杜绝 HTML 解析风险 - 解析错误响应前,先判断字段是否存在:
const msg = errors?.email || detail || message || '请检查输入' - 若后端返回多字段错误,按字段映射到对应 ID 容器:
document.getElementById('email-error')?.textContent = errors.email - 连续报错(如实时校验)加 300ms 节流,避免
aria-live区域高频重计算拖慢 VoiceOver
最易被忽略的一点:错误容器的 id 和输入框的 aria-errormessage 值,必须在每次 JS 更新后同步重设——不是只写一次就完事。DOM 存在、ID 匹配、状态标记、播报机制,四者缺一不可,漏掉任意一环,无障碍就断在半路。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











