:is() 提供原生「或」逻辑,如 :is(.btn, .link, .cta),但不可嵌套于 :not() 中;:not() 仅支持简单选择器,禁用 :has() 等关系伪类;:has() 支持「与」逻辑但性能开销大且兼容性有限。

用 :is() 实现「或」逻辑,但别乱套在 :not() 里
想匹配多个类名中任意一个?:is(.btn, .link, .cta) 就是你要的。它不是函数调用,而是原生「或」语义:只要元素匹配其中任一选择器,整条规则就生效。但注意::is() 不能塞进 :not() 当参数,比如 :not(:is(.disabled, .hidden)) 在 Chrome 稳定版里直接报语法错误——目前只有 Firefox 111+ 和 Safari 16.4+ 支持,生产环境应避免。
常见误用场景:
- 单个选择器硬套
:is(.foo):毫无意义,还多一次解析 - 写成
:is(.a .b, .c .d)以为能分别匹配两组后代链:实际是「整体可选」,语义模糊且可读性差 - 依赖
:is()降权:它会继承内部最高特异性(如:is(.x, #y) span权重等同于#y span),和:where()不是一回事
用 :not() + :has() 模拟「不含某子元素」的「与」逻辑
:not(:has(.error)) 看似直觉,但这是非法写法——CSS 规范禁止在 :not() 参数中使用 :has() 或任何关系选择器。:not() 只接受简单选择器(类、ID、属性、伪类如 :hover),不支持嵌套逻辑。
正确做法是外提 :has(),再用 :not() 包裹整个主体:
立即学习“前端免费学习笔记(深入)”;
- 要选「不包含
.disabled按钮的表单」:写form:not(:has(button.disabled)) - 只检查直接子元素?用
:has(> button.disabled),性能略好,语义更准 - 要同时排除
.error和.warning?只能拆开:div:not(:has(.error)):not(:has(.warning))
注意::has() 不支持向上查父级,也不支持兄弟选择器(如 :has(+ .sibling)),所以「父元素有某类」必须靠父元素自己暴露状态(比如加 aria-disabled 属性)。
属性选择器天然支持「与」,但「或」得靠逗号或 :is()
多个属性条件连写,比如 a[href^="https"][href$=".org"],就是「与」逻辑:必须同时满足开头是 https 且结尾是 .org。但若想表达「href 包含 a 或 b 或 c」,传统方式只能重复写三遍:
a[href*="a"], a[href*="b"], a[href*="c"] { color: red; }
现代写法更干净:
a:is([href*="a"], [href*="b"], [href*="c"]) { color: red; }- 兼容老浏览器?双写:
a[href*="a"], a[href*="b"], a[href*="c"] { color: red; }+a:is([href*="a"], [href*="b"], [href*="c"]) { color: red; }
别指望 :is() 能绕过权重规则——它不改变选择器优先级,只是简化写法。
:has() 的支持度和性能代价容易被低估
:has() 目前仅在 Chrome 105+、Safari 15.4+、Firefox 121+ 原生支持;旧版浏览器(包括所有 IE)完全无视。更重要的是,它会触发反向查询(从子元素向上找祖先),在复杂 DOM 中可能造成 layout thrashing。尤其当用于 @keyframes 或伪元素(如 ::before)时,多数浏览器直接禁用。
真正该警惕的点:
- 没做降级:没提供
:not(:has(...))的 fallback 样式,导致老浏览器完全无样式 - 滥用深层后代匹配:
:has(.deep .nested .item)比:has(> .direct)开销大得多 - 误以为它能跨层级否定:比如「按钮是 primary 类型且不在禁用表单内」,无法用
:has()表达,必须靠父容器显式标记


















