:has()可替代JS监听列表项状态,如.list:has(.item.active)高亮列表、.list:has(.item.error)触发表单错误提示;但不支持:hover/:focus等动态伪类,且Safari 15.4–16.3对+/~兄弟选择器支持不稳定。

用 :has() 替代 JS 监听列表项状态
列表项(如 .item)的交互状态(选中、悬停、错误、加载中)过去常靠 JS 绑定 class 或监听事件,再手动更新父容器样式。现在一行 :has() 就能触发父级响应,省去 MutationObserver 或反复 classList 操作。
常见场景包括:点击切换 .item.active 时高亮整个 .list;.item 内含 .error-badge 时给整行加红边;悬停某个 .item 时显示其右侧的 .action-bar。
-
.list:has(.item.active)→ 用于全局启用/禁用操作按钮 -
.list:has(.item.error)→ 触发表单级错误提示栏显隐 -
.list:has(.item:hover)→ 激活顶部工具栏的“批量操作”入口
:has() 中的兄弟选择器必须写对方向
:has() 只能“向后”或“向下”匹配,不能向前找前兄弟,也不能跨祖先向上找父节点以外的元素。想控制“列表项之后的叠加层”,得让 .list 和 .overlay 共享同一个父容器,再用 ~。
错误写法:.item:has(+ .overlay) —— .item 不是 .overlay 的前一个兄弟,它俩没直接关系;正确做法是把条件移到共同祖先上。
立即学习“前端免费学习笔记(深入)”;
- ✅
.container:has(.item:nth-child(1):hover) ~ .overlay-one(.overlay-one是.container后面的兄弟) - ✅
.container:has(.item:hover) > .toolbar(.toolbar是.container的直接子元素) - ❌
.overlay:has(~ .item:hover)——:has()不能反向查“前面有没有”,语法合法但永远不匹配
别在 :has() 里塞 :hover 或 :focus
:has() 括号内只接受“静态可判定”的选择器。浏览器在样式计算阶段无法预知用户是否即将 hover 或 focus,所以 :hover、:focus-within、伪元素(::before)等一律被忽略,且静默失效 —— DevTools 里根本看不到该规则。
实际开发中容易误以为 .item:has(button:hover) 能高亮整行按钮组,结果完全没反应。替代方案是用 JS 添加临时 class,或改用 :has(button:focus-visible)(仅限键盘焦点,兼容性略好)。
- ✅
.item:has(input:invalid)、.item:has([data-status="loading"]) - ✅
.item:has(> .icon[src*="warning"])(属性匹配稳定) - ❌
.item:has(button:hover)、.item:has(.trigger:focus)、.item:has(::after)
Safari 15.4–16.3 对 +/~ 支持不稳定
虽然 Safari 15.4+ 声称支持 :has(),但在 15.4 到 16.3 版本中,:has(+ B) 和 :has(~ B) 极易被静默忽略。比如 .header:has(nav input:checked) + main 在这些 Safari 版本里不会生效,但换成 .header:has(nav input:checked) ~ main 有时又可以 —— 行为不一致,不可依赖。
生产环境若需兼容,建议:优先用 ~ 替代 +;避免嵌套 :has();对关键路径做降级处理(如 fallback class 或 JS 补位)。
- Chrome 105+、Firefox 121+:
+和~均稳定 - Safari ≥16.4:已修复,可放心用
+ - Electron 应用:确认内核 Chromium ≥105,否则
:has()规则根本不会解析
真正难的不是写出 :has(),而是判断 DOM 结构是否允许它“看到”目标节点 —— 它不认 JS property、不跨 Shadow DOM、不响应动态伪类,所有条件都必须落在真实 attribute 和静态结构上。


















