错误提示必须紧邻input作为同级兄弟节点,禁用label或父容器末尾放置;需用data-error-target精准关联、ARIA属性增强可访问性,并在preventDefault后手动处理校验全流程。

错误提示必须紧挨 input,不能塞进 label 或父容器末尾
表单验证失败时,错误文案必须作为 <input> 的同级兄弟节点插入,比如紧跟在 <input> 后面放一个 <div class="error"></div>。插进 <label> 里会破坏语义结构,屏幕阅读器可能跳过或重复播报;放在父容器末尾则视觉动线断裂,键盘用户 tab 到输入框后,错误信息不在视线路径上,根本找不到。
实操建议:
- 每个
<input>都配一个专属提示容器,用data-error-target属性指向它,例如:<input name="email" data-error-target="#error-email"><div id="error-email" class="error" role="alert" aria-live="polite"></div> - 用
CSS控制提示区域:设display: block、margin-top: 0.25rem,避免行内元素换行错位 - 禁用
title或placeholder承载错误文案——前者移动端不可触达,后者 focus 后即消失,都不满足 WCAG 2.1 “持久可见、明确关联” 要求
preventDefault() 后必须手动接管全部提示逻辑
一旦你在 submit 事件里调用了 event.preventDefault(),浏览器原生的气泡提示、红框、:invalid 样式更新就全部失效。这不是 bug,是设计使然:你已声明“我要自己处理”,那就得自己负责错误定位、DOM 插入、焦点聚焦、ARIA 状态更新。
常见翻车点:
立即学习“前端免费学习笔记(深入)”;
-
setCustomValidity("邮箱格式不对")调了,但没清空旧状态(漏掉input.setCustomValidity("")),导致后续输对了也一直报错 - 校验失败后没调
input.focus(),键盘用户卡在提交按钮,完全不知道哪错了 - 用
document.querySelector(".error")更新提示,结果永远只改第一个,其余字段错误不显示
正确做法:在 submit 处理函数里,遍历所有字段,对每个 input 单独执行:el.checkValidity() → 判断是否失败 → 清空/写入对应 data-error-target 元素 → 设 aria-invalid="true" → el.setAttribute("aria-describedby", errorId)。
实时反馈别依赖 invalid 事件,改用 input + blur + checkValidity()
invalid 是个冒泡事件,只在表单提交或显式调用 reportValidity() 后触发,不适合做输入过程中的即时反馈。想让用户每敲一个字符、或失焦瞬间就知道对错,得监听 input 和 blur,并主动调 checkValidity()。
注意细节:
-
input事件适合防抖型校验(如邮箱格式),但别每次按键都调reportValidity()——它会强制弹气泡,打断输入体验 -
blur更稳妥:用户离开字段时校验一次,既及时又不干扰 -
checkValidity()只返回布尔值,要取错误文案得读input.validationMessage;设自定义文案必须用setCustomValidity("xxx"),且非空才生效 - 初始加载时想让必填字段立刻显示红框?得手动对每个
required字段调一遍checkValidity(),否则:invalid:user-invalid伪类不会激活
后端返回 400 错误时,字段映射和清理必须严格
后端返回的错误对象(如 {"email": ["已被注册"], "password": ["至少8位"]})和前端 DOM 字段不是自动对齐的。字段名大小写、下划线/驼峰、嵌套层级稍有差异,错误就会塞错位置甚至堆叠不消。
关键动作只有两个:映射 + 清理。
- 服务端字段名必须和
input[name]完全一致(包括大小写、符号),这是最省事的约定 - 渲染新错误前,先清空所有已有提示:
form.querySelectorAll(".error").forEach(el => el.textContent = ""),避免旧错误残留 - 每个字段的提示容器必须固定存在且可被唯一定位,推荐用
data-error-target+ ID 关联,而不是靠顺序或 class 查找 - 别用全局 toast 替代字段级提示——用户需要知道“哪错了”,不是“错了”
最易被忽略的是清理时机:后端错误渲染完,用户修改字段后没再次提交,旧错误仍挂着;或者连续两次提交,错误文案叠加显示。这些都得靠 JS 显式控制,没有银弹。



















