aria-describedby 不是 label 替代品,必须配合已有可访问名称的控件使用;其值须为真实、唯一、大小写敏感的 id,支持空格分隔多个 id 以按 dom 顺序朗读;避免 display:none,推荐用 hidden 或绝对定位隐藏;动态渲染需确保 id 全局唯一且服务端客户端一致。

aria-describedby 必须配合已有可访问名称的控件使用
单独给没 label、没 aria-label、也没 aria-labelledby 的 <input> 加 aria-describedby,屏幕阅读器只会读出描述,不读“这是个啥”——用户根本不知道自己在填什么。它不是 label 替代品,而是补丁。
常见错误现象:<input type="text" aria-describedby="hint"><div id="hint">请输入6位数字</div> —— 这段 HTML 在 VoiceOver 或 NVDA 下只读“请输入6位数字”,前面缺“验证码”三个字。
- 必须先确保控件有可访问名称:要么用
<label for="xxx"></label>显式关联,要么加aria-label或aria-labelledby -
aria-describedby的值必须是真实存在的 ID,大小写敏感,且不能含空格或特殊字符(如hint-1可以,hint 1不行) - 被引用的元素建议用
<p></p>或<div>,避免用 <code><span></span>—— 部分旧版 JAWS 对内联元素朗读支持弱多个说明文本怎么用空格分隔 ID
一个输入框可能同时需要格式提示 + 实时校验错误 + 示例值,
aria-describedby支持多个 ID,用空格连接,屏幕阅读器按 DOM 顺序依次朗读。例如:
<input id="password" aria-describedby="pw-hint pw-error pw-example">-
pw-hint指向“至少8位,含大小写字母和数字” -
pw-error指向动态插入的错误消息,如“密码太短” -
pw-example指向“示例:Abc12345” - 注意:ID 的 DOM 出现顺序要和语义顺序一致;如果
pw-error在 DOM 中排最前,但逻辑上应在提示之后读,就容易造成认知混乱 - 旧版 JAWS 对超过 2 个 ID 的支持不稳定,生产环境建议控制在 2 个以内
隐藏提示文本但不让屏幕阅读器跳过
视觉上不想占空间,又想让读屏能读到?不能用
display: none或visibility: hidden—— 这两类 CSS 会让多数读屏直接忽略内容。- 推荐用
position: absolute; left: -9999px;或clip: rect(0 0 0 0); - 或者直接加
hidden属性(HTML5 原生),它对读屏友好,且语义明确 - 不要把整段帮助文档塞进
aria-describedby所指向的元素里——读屏会一口气全读出来,打断操作流;超过两句话,考虑改用弹出式帮助或aria-details(兼容性差,仅 Safari 17+ / Chrome 125+ 支持)
动态渲染组件里的 ID 冲突风险
React/Vue 中循环生成多个相同结构的表单项时,
id容易重复,导致aria-describedby指向第一个匹配项,其余失效——这种问题不会报错,人工测试极难发现。- ID 必须全局唯一;建议用组件级前缀 + 唯一标识,比如
user-form-password-hint-${uuid} - 服务端渲染或 SSR 场景下,确保服务端和客户端生成的 ID 一致,否则 hydration 后 ID 可能错乱
- 自动化测试里加断言:检查
document.getElementById(xxx)是否存在,且其textContent非空
最麻烦的不是写错,而是写对了但 ID 被删了、改名了、或 DOM 节点没挂载就绑了属性——这些都会让
aria-describedby彻底静默,而你完全收不到任何警告。 -











