:has() 可使父容器响应子元素表单校验状态,如 div.field:has(input:invalid) 设置红框;需配合原生验证属性,注意 Safari 兼容性与性能开销。

用 :has() 判断表单控件是否 valid/invalid
现代浏览器中,:has() 能直接根据子元素状态反向影响父容器样式——这正是表单校验样式的痛点:传统 CSS 无法从 input:valid 向上影响包裹它的 label 或 div.field。现在可以了。
关键在于把校验逻辑“下沉”到 HTML 结构里,让待响应的容器成为 :has() 的主体,而 input 是其后代(或相邻兄弟):
div.field:has(input:invalid) {
border-color: #e53e3e;
}
div.field:has(input:valid) {
border-color: #38a169;
}
注意::has() 不支持伪元素和某些伪类组合(如 :has(:focus:invalid) 在 Safari 16.4+ 才稳定),且不能用于 @keyframes 或选择器开头(如 :has(input):hover 合法,但 :has(input) .hint 不合法)。
关联错误提示文案显示与隐藏
不用 JS 控制 display,也能让 .error-message 随输入状态自动出现/消失——前提是它和 input 处于同一父容器下,且结构可被 :has() 捕获。
-
div.field:has(input:invalid) .error-message→ 显示错误文案 -
div.field:has(input:not(:invalid)) .error-message→ 隐藏(注意不是:valid,因为初始空值常为:invalid但非用户已输错) - 若错误提示在
input后面作为相邻兄弟,可用input:invalid + .error-message,更轻量,但灵活性低
示例结构必须是:
<div class="field"> <label>邮箱</label> <input type="email" required><span class="error-message">请输入有效邮箱</span> </div>
:has() 与原生表单验证属性的配合要点
:has() 响应的是浏览器原生校验状态,依赖 required、type="email"、pattern 等属性触发 :invalid / :valid。它不感知自定义 JS 校验结果。
- 空值 +
required→ 触发:invalid(但提交前可能不显示红框,取决于:user-invalid行为) - 输入后失焦(
blur)才稳定触发:invalid,实时校验需搭配input事件 + JS 设置setCustomValidity() -
input:invalid:not(:user-invalid)可区分“未交互过的无效态”和“用户改过又输错”,但:has()无法直接嵌套这个判断,得靠额外 class 控制
也就是说::has(input:invalid) 在用户还没输任何内容时就可能生效,视觉上容易“过早报错”。真实项目中常要加一层 .touched class 配合 JS,再写 div.field.touched:has(input:invalid)。
Safari 和旧版 Chrome 的兼容性兜底策略
Chrome 105+、Firefox 121+、Safari 15.4+ 支持 :has(),但 Safari 15.4–16.3 对 :has(input:invalid) 存在闪烁或不重绘问题;部分安卓 WebView 完全不支持。
- 服务端或构建时可注入降级 CSS:
html.no-has-support .field:invalid(需 JS 检测并打 class) - 避免纯依赖
:has()实现核心功能,至少保留 JS 绑定 class 的 fallback 路径 - 不要用
:has()替代aria-invalid属性——无障碍仍需 JS 同步设置
最易被忽略的一点::has() 的性能开销比普通选择器高,尤其当匹配大量表单项时,建议限制作用域(比如只对 .form--validated 下的 .field 启用),别全局写 :has(input:invalid)。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











