label和input的for/id必须逐字符完全一致,否则屏幕阅读器直接跳过字段名;禁用隐式包裹,严格使用显式关联或唯一直接子节点包裹;select和radio/checkbox组需遵循紧邻布局与fieldset/legend规范;aria-label和placeholder不可替代label。

label 和 input 的 for/id 必须严格匹配
屏幕阅读器只认 for 和 id 的字面完全一致,大小写、连字符、空格差一个都会断开关联。常见错误是视觉上看着对,但 DOM 里 for="email" 对应了 id="Email" 或 id=" email ",结果读屏器只报“编辑框”,不报“邮箱地址”。
- 显式写
<label for="username">用户名</label><input id="username">,确保两个字符串逐字符相等 - 避免用隐式包裹(
<label>用户名<input></label>),JS 动态插入节点或中间加注释后容易失效 -
<select></select>更敏感:iOS VoiceOver 要求<label></label>和<select></select>在 DOM 中紧邻,中间插个<p></p>就可能点 label 焦点不进去
placeholder 不是 label,禁止单独使用
视觉上 placeholder 消失后字段就“没名字”了,辅助技术基本不读它。很多团队用 placeholder="手机号" 替代 <label></label>,结果键盘用户 tab 进去,听到的只是“文本框”,根本不知道该填啥。
- 首选补全
<label for="tel">手机号</label><input id="tel" type="tel"> - 极少数上下文明确场景(如搜索框图标旁无文字),可用
aria-label="搜索网站内容" - 已有可见文本(比如图标旁写了“密码”),优先用
aria-labelledby="password-label-id",避免重复播报
错误提示必须预置 DOM 并配对 aria-invalid + aria-describedby
JS 动态创建错误 <div> 后读屏器没反应?大概率是因为元素不在初始 DOM 中。NVDA、VoiceOver 只监听已挂载在可访问性树里的节点变化。
<ul>
<li>页面加载时就得存在 <code><div id="email-error"></div>,哪怕内容为空
aria-invalid="true",并用 aria-describedby="email-error" 绑定——不能只设状态不绑文案textContent,别用 innerHTML(XSS 风险 + 可访问性树不触发)aria-invalid 和 aria-describedby,否则旧错误残留按钮类交互必须语义化,别靠 role="button" 补丁
<div onclick="submit()">提交</div> 对鼠标用户没问题,但键盘用户 Tab 进不去、Enter 按了没反应、读屏器当它是段落。
- 直接用
<button type="button">提交</button>——原生支持焦点、空格/Enter 触发、自动被读屏识别为按钮 - 如果是图标按钮(只有
<svg></svg>),必须加aria-label="删除此项"或aria-labelledby - 真要用
div,得手动加role="button"、tabindex="0"、监听keydown处理 Enter/Space,还容易漏 focus outline











