CSS的:is()与:where()并不是新语法糖,它们解决的是选择器分组和优先级控制两个长期痛点。传统写法需要把多个选择器拆开重复声明,维护时容易漏改;而这两个伪类可以把共同结构合并,同时保留或重置特异性,让样式层叠更可预测。

一、先把选择器分组讲清楚,is与where到底省了什么
在没有:is()之前,如果要给多种容器内的标题设置统一样式,常见写法是把每个选择器都列出来。例如文章、章节、侧栏里的二级标题都需要相同的间距,代码会变成三行甚至更多行。样式本身没有变化,变的是上下文选择器。这种重复一旦进入大型项目,就会出现一个很实际的问题:改一处必须改三处,漏掉一处就会造成视觉不一致。
/* 传统写法 */
article > h2,
section > h2,
aside > h2 {
margin-top: 2rem;
line-height: 1.25;
}
/* 使用 is 分组 */
:is(article, section, aside) > h2 {
margin-top: 2rem;
line-height: 1.25;
}
:is()的作用是把一组选择器当成一个整体来使用。上面这行代码在语义上仍然会命中文章、章节、侧栏的直接子级标题,但书写时只保留了一处公共结构。对于嵌套较深的选择器,收益会更明显。比如原本要写.page .article h2, .page .section h2, .page .aside h2,现在可以收敛成:is(.page .article, .page .section, .page .aside) h2。代码长度下降之外,真正重要的是修改入口变少了。
:where()在分组能力上和:is()很像,同样可以接收一组选择器。区别在于它不只是缩短写法,还会改变权重计算。这个差异决定了两者不是简单二选一,而是分别服务于不同的样式策略。理解这一点之后,再谈效率,才不会被误用拖慢维护速度。
二、权重规则才是分水岭,is保留优先级,where主动降权
CSS选择器最麻烦的地方往往不是写不出,而是写出来之后覆盖关系不稳定。一个组件默认样式写得越通用,越容易被业务样式覆盖;但如果默认样式写得太重,又会影响后续主题定制。:is()的特异性规则是取参数列表中最强选择器的权重。换句话说,它保留了“这组选择器里谁最厉害”的信息。
/* is 的特异性取参数中最高项 */
:is(.card, #hero) p {
color: #222;
}
/* 等价于 */
.card p,
#hero p {
color: #222;
}
如果参数里同时存在类选择器和ID选择器,那么:is()计算出的权重会接近ID选择器这一档。这个特性适合需要精确控制的场景。比如你希望某些容器内的文本默认继承主题色,但又不想因为合并选择器而降低原本应该具备的优先级,就可以用:is()。它让分组后的选择器仍然保留较强的覆盖能力,不会因为写成一行而变得太弱。
:where()则完全不同。无论参数里的选择器多复杂、多具体,:where()本身的特异性都按零计算。这个规则看起来简单,实际却非常适合做兜底样式、基础样式、工具类样式。因为它不会轻易压住后面的业务选择器,也不会强迫开发者频繁使用!important来对抗优先级。
:where(.button, .link, .tab) {
border-radius: 0.5rem;
}
/* 后续普通类选择器可覆盖 */
.button {
border-radius: 0.25rem;
}
在实际项目中,可以这样理解::is()适合“我需要合并写法,但优先级不能丢”;:where()适合“我只希望提供默认值,谁都可以覆盖我”。两者都提高编写效率,但一个偏向精确控制,一个偏向低侵入复用。选错工具,效率提升会变成调试成本。
三、实战场景,组件状态、表单控件、工具类与兜底样式
组件样式里经常会出现一组状态选择器。比如按钮有禁用、加载、错误、成功等状态,卡片有悬浮、聚焦、激活等状态。如果每个状态都单独写一遍公共样式,代码会迅速膨胀。使用:is()可以把状态聚合起来,同时保留足够的权重,确保组件内部状态能稳定生效。
:is(.button.is-disabled, .button.is-loading, .button.is-error) {
cursor: not-allowed;
opacity: 0.72;
}
:where(.card, .panel, .modal) {
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 0.75rem;
}
表单控件也是典型场景。输入框、下拉框、文本域经常共享同一套外观,但它们的元素名和属性选择器不同。传统写法会把多个选择器拆成多行,使用:is()后可以集中表达“这些控件都应用同一套基础样式”。当项目需要统一表单尺寸、边框、内边距时,这种写法能减少重复声明,也便于后续统一调整。
:is(input[type="text"], input[type="email"], input[type="password"]) {
width: 100%;
padding: 0.5rem;
border: 1px solid #cbd5e1;
border-radius: 0.375rem;
}
:is(input[type="checkbox"], input[type="radio"]) {
width: auto;
padding: 0;
}
工具类样式更适合:where()。比如一组基础排版类、间距类、圆角类,它们应该作为默认值存在,而不是作为强覆盖存在。如果工具类权重过高,业务组件反而要写更复杂的选择器才能覆盖。用:where()包裹后,工具类既保持复用能力,又不会成为层叠链路上的障碍。这在设计系统或组件库中尤其常见。
:where(.text-sm, .text-base, .text-lg) {
line-height: 1.6;
}
:where(.rounded, .rounded-lg) {
overflow: hidden;
}
四、容易踩的坑,兼容、可读性、调试与误用
第一个坑是兼容性。现代浏览器对:is()和:where()的支持已经比较成熟,但旧环境可能无法识别这些伪类。一旦浏览器不认识:is(),整条选择器规则可能被直接丢弃,而不是部分生效。因此,如果项目仍需覆盖老旧浏览器,关键样式不能只依赖新伪类,最好保留展开写法作为兜底。
/* 新写法 */
:is(article, section, aside) > h2 {
margin-top: 2rem;
}
/* 兼容旧环境的兜底写法 */
article > h2,
section > h2,
aside > h2 {
margin-top: 2rem;
}
第二个坑是可读性。选择器压缩后看起来更短,但如果参数列表过长、嵌套层级过深,维护者反而需要花更多时间理解命中范围。尤其当一组选择器之间没有明显语义关系时,强行合并只会让代码变成“看起来聪明,读起来费劲”的写法。CSS的第一目标是可维护,不是字符最少。
- 同一组选择器应具有共同语义,例如同一类容器、同一类状态、同一类表单控件。
- 不要把完全无关的选择器塞进同一个
:is()或:where()。 - 在需要明确优先级的地方慎用
:where(),避免样式被意外覆盖。
第三个坑是调试。浏览器开发者工具在展示选择器时,可能不会像展开写法那样直观。遇到样式不生效时,需要同时关注选择器是否被支持、参数是否命中、特异性是否符合预期。尤其使用:where()做基础样式时,如果后续样式覆盖失败,问题可能不在选择器没命中,而在你误以为默认样式应该拥有更高权重。
五、什么时候不该用,保持选择器意图清晰更重要
如果一段样式只作用于一个明确组件,且选择器本身已经很短,就没有必要强行引入:is()或:where()。新选择器的价值来自复用和聚合,而不是替代所有普通写法。对于一次性样式、局部微调、明确业务规则,直接写选择器往往更容易理解。CSS工具应该服务于意图清晰,而不是制造抽象层级。
比较稳妥的实践方式是:公共基础样式、工具类、组件默认值优先考虑:where();状态聚合、容器分组、需要保留权重的合并优先考虑:is();关键兼容场景保留展开写法。这样既能减少重复声明,也能让样式层的优先级关系保持可预测。选择器写得短是表象,真正提高的是维护效率。