显式关联label与input必须100%严格匹配for和id值,大小写、符号、空格均需一致;禁用隐式包裹;required校验需配动态aria-invalid和aria-live错误提示;自定义控件须透传焦点与键盘事件;type属性须真实反映语义。

label 和 input 的显式关联必须 100% 严格匹配
很多表单重构后无障碍测试仍失败,根源常在 for 和 id 值表面“看起来一样”,实则存在隐性差异。这不是浏览器兼容问题,而是 DOM 层面的硬性断连。
-
for值必须与input的id完全相等:大小写、连字符、下划线、首尾空格,一个都不能多或少 - 模板渲染(如 React/Vue 循环)中动态生成
id时,for必须同步拼接,不能靠字符串插值后手动修正 - 禁用隐式包裹写法:
<label>邮箱<input name="email"></label>——DOM 中插入注释、换行或 Fragment 节点会直接切断关联 - 不要用
aria-label或title“补救”缺失的label:它们无法触发点击聚焦,且部分屏幕阅读器(如旧版 NVDA)会跳过或重复播报
required 校验必须配 error message,且需动态更新 aria-invalid
加了 required 却没处理校验反馈,等于把错误提示权交给浏览器——结果是英文原生提示、无焦点管理、不播报、不联动视觉样式。
- 提交前调用
input.checkValidity(),失败时用input.setCustomValidity("手机号格式不对")设置中文文案 - 校验通过后必须调用
input.setCustomValidity("")清空,否则后续checkValidity()永远返回 false -
aria-invalid必须 JS 动态设置:input.setAttribute('aria-invalid', 'true'),静态写死会导致页面一加载就报错 - 错误消息容器需带
aria-live="polite",并用aria-describedby关联到 input,例如:<input aria-describedby="phone-error"><div id="phone-error" aria-live="polite" hidden>请输入11位手机号</div>
自定义控件(如下拉、日期)必须透传焦点和键盘事件
用 div + ul 替换原生 select 是最常见无障碍退化点。视觉再精致,若不手动实现 ARIA 和键盘逻辑,对屏幕阅读器用户就是不可操作的黑盒。
- 原生
select开箱即用,别为样式轻易替换;真要自定义,必须实现:role="combobox"、aria-expanded、aria-controls、aria-activedescendant - 键盘支持至少包括:
ArrowDown/ArrowUp切换选项、Enter或Space确认、Escape关闭、Tab进出组件 - 点击
label文本必须能聚焦到自定义控件内部第一个可交互节点(如输入框或按钮),不能只聚焦外层容器 - iOS VoiceOver 对
label和select的 DOM 位置敏感:两者应紧邻,中间不能插<p></p>、<div> 等块级元素 <h3>type 属性必须真实反映语义,而非仅服务 UI 行为</h3> <p><code>type="number"在移动端唤起数字键盘,但它的核心价值是告诉辅助技术“这是数值字段”。滥用它反而破坏语义,让屏幕阅读器把邮编读成“一千零二十三万四千五百六十七”,而不是“零一零、二三四五、六七八九”。-
type="tel"不只为调数字键盘——TalkBack 会按电话规则分段朗读,VoiceOver 会触发语音控制指令“填写电话” -
type="email"是邮箱字段的语义锚点,禁用type="text"+ JS 掩码,那会让密码明文被读出 -
type="number"仅用于真正参与计算的字段(年龄、金额);邮编、手机号、学号一律用type="text"+inputmode="numeric"+pattern="[0-9]*" -
valueAsNumber不可靠:非法输入时返回NaN,IE 完全不支持;提交前务必用parseFloat(input.value) || 0手动转换
-











