:invalid 伪类仅响应浏览器原生验证,无法感知 JS 的 setCustomValidity() 或后端错误;需用 data-valid 属性与 CSS 变量统一映射多源验证状态,并同步 aria-invalid 等可访问性属性。

为什么直接用 :invalid 无法响应 JS 验证结果
因为 :invalid 只由浏览器原生验证机制触发,比如用户删空 required 字段后失焦,或输入不符合 type="email" 格式。JS 调用 setCustomValidity() 或后端返回的错误状态,完全不会影响这个伪类——它压根不感知。
更麻烦的是:即使你手动调用 input.reportValidity(),虽然能强制重绘并弹提示,但会打断用户操作流;而 checkValidity() 只返回布尔值,样式毫无变化。
- 纯 CSS 方案在业务验证(如“用户名已被注册”)面前彻底失效
-
:invalid:not(:placeholder-shown)这类技巧只能缓解 placeholder 误判,解决不了根本问题 - 多个验证来源(前端规则 + 后端响应 + 实时 API 校验)必须统一映射到一个可控的状态出口
用 data 属性 + CSS 自定义变量做状态中枢
核心思路是把验证结果“导出”为 HTML 层级的明确信号,再让 CSS 通过属性选择器和变量承接。JS 不操作 style,只改 data- 属性;CSS 用 [data-valid="true"] 这类选择器驱动变量,再用 hsl()、var(--border-color) 等统一控制视觉。
- 必须加引号:
[data-valid="true"],不能写成[data-valid=true](Safari 和旧版 Chrome 解析异常) - 避免用布尔属性如
data-valid,因为移除属性后选择器无法匹配[data-valid]的“假值”状态 - 推荐三态变量:
--validation-state: idle | pending | error | success,比二值更适应真实流程 - CSS 中用
color: hsl(0, 80%, 50%)替代写死#e74c3c,后续换主题只需改变量值
如何让边框色、图标、提示文字联动更新
不要为每种状态写重复的边框/图标/文字规则。把所有视觉元素都绑定到同一个变量上,用 CSS 条件函数或分层选择器集中控制。
立即学习“前端免费学习笔记(深入)”;
- 边框色:
border-color: hsl(calc(var(--hue, 0) * 1), 80%, 55%);,其中--hue由 JS 根据状态设为 0(红)、120(绿)、30(黄) - SVG 图标:
background-image: url("data:image/svg+xml,%3csvg...fill='%23' + encodeURIComponent(hsl(...)) + '%3e");(实际需 URL 编码) - 错误提示显隐:
.error-message { opacity: clamp(0, var(--error-opacity, 0), 1); transition: opacity 0.2s; },JS 设style.setProperty('--error-opacity', '1') - 注意:IE 不支持
hsl()变量插值,若需兼容,改用预设类名切换(如.state-error)更稳妥
容易被忽略的可访问性与动效细节
状态变更时,光有颜色变化远远不够。屏幕阅读器需要明确的语义信号,用户也需要非颜色维度的反馈。
- 每次设置
data-valid,同步更新aria-invalid("true"/"false")和aria-describedby(指向对应.error-message的 ID) - 避免仅靠抖动或闪烁做错误提示——WCAG 2.1 明确禁止高频闪烁(可能诱发癫痫),改用平滑
transform: scale(0.98)+transition - 初始加载时,如果字段已有服务端错误,JS 必须在 DOM ready 后立即设置
data-valid="false"和对应提示,否则首屏无反馈 - 移动端 focus 时软键盘会遮挡提示,
.error-message最好用position: absolute+top: 100%,而非默认文档流


















