错误提示必须紧邻input元素后插入,使用role="alert"和aria-live="polite"确保屏幕阅读器可读;禁用display:none而用visibility:hidden保持布局稳定;动态验证需同步更新required、aria-invalid等属性及DOM状态。

错误信息必须插在 <input> 后面,不能塞进 <label>
DOM 位置错了,屏幕阅读器就找不到错误。插在 <label> 里会破坏语义,语音播报可能跳过或重复;插在父容器末尾则视觉动线断裂,用户扫一眼根本对不上哪个字段出错。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 每个
<input>后紧跟一个<div class="error" role="alert" aria-live="polite"></div> - 用 CSS 控制
display: block和margin-top,避免行内元素换行错位 - 别用
aria-describedby指向远距离的元素——ID 对得上也没用,焦点切换时语音反馈常失效 - 多个字段共用同一段 JS 验证逻辑时,别写
document.querySelector(".error"),永远只改第一个;改用el.dataset.errorTarget精准定位
setCustomValidity() 不是“设完就显示”,必须配 reportValidity() 或提交触发
只调 setCustomValidity("手机号格式错误"),UI 不会变;它只是改了 validity 状态,不触发浏览器反馈。原生验证只在 submit 时批量检查,JS 赋值、粘贴、空值判断全都不管。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 监听
input事件时,先调el.setCustomValidity("")清空旧状态,再根据规则重设 - 想实时提示?手动调
el.reportValidity(),但注意:它会弹原生气泡,破坏 UI 一致性 - 更稳妥的做法是结合
checkValidity()+ 自定义 DOM 提示,完全绕开气泡 -
pattern必须写成pattern="^[0-9]{11}$",否则正则没锚定,abc12345678901def这种也会通过
隐藏错误提示别用 display: none,用 visibility: hidden
display: none 会导致布局重排,输入框上下元素“抽搐”;而 visibility: hidden 保留占位,视觉稳定,也兼容屏幕阅读器对 role="alert" 的播报逻辑。
常见错误现象:
- 用
innerHTML = ""清空提示后,忘了重置visibility,后续报错不显示 - 表单提交批量验证时,没对每个字段单独控制对应提示的显隐,结果所有提示一起弹出或一起消失
- 没在 JS 中同步清除
aria-invalid="true"和aria-describedby,导致辅助技术误判状态
动态验证时,required 属性要随条件开关,且禁用字段也要处理
选“个人”时身份证号必填,选“企业”时营业执照号才必填——这种逻辑不能只靠 JS 校验,得真正更新 DOM 层级的约束状态,否则 checkValidity() 结果不准,键盘用户和屏幕阅读器也感知不到变化。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 用
el.required = true/false动态开关,别只靠 JS 判断 - 字段禁用时(如
license.disabled = true),记得同时设license.required = false,否则checkValidity()仍会校验它 - 初始状态要用
dispatchEvent(new Event('change'))主动触发一次,避免首次加载漏逻辑 - 别把错误文案塞进
title或placeholder:前者移动端不可用,后者 focus 后就消失,都不满足 WCAG “持久可见、明确关联” 要求



















