:where()
是一个非常有用的 CSS 伪类函数 (CSS functional pseudo-class),它允许你将一组选择器打包在一起,并具有一个关键特性:它的特异性(Specificity)总是为零(0)。
打开 antd 的开发者模式,发现 antd 就用了不少这样的样式:

看一下他的兼容性,大部分主流浏览器都可以了:

一、:where()
的基本概念
:where()
是一个函数式伪类,它接受一个逗号分隔的选择器列表 作为参数。它会选中任何能被该列表中的任一选择器所匹配的元素。
语法:
css
:where(<选择器列表>) {
/* CSS 样式 */
}
示例:简化选择器 假设你希望同时给 h1
、h2
和 h3
标签设置一个相同的 margin-top
样式,传统写法是:
css
/* 传统写法 */
h1, h2, h3 {
margin-top: 1.5em;
}
使用 :where()
后,可以写成如下的形式:
css
/* 使用 :where() */
:where(h1, h2, h3) {
margin-top: 1.5em;
}
二、:where()
的核心特性:特异性为零(Specificity: 0)
这是 :where()
与其孪生兄弟 :is()
最大的区别和价值所在。
:where()
的特异性: 始终是 0, 0, 0 。无论你传入的内部选择器有多复杂(例如,包含 ID 或类选择器),:where()
本身都不会贡献任何特异性值。:is()
的特异性: 取决于其参数列表中特异性最高的那个选择器。
特异性是 CSS 的一个核心概念,它是一个算法,用于确定当多个 CSS 规则试图为同一个元素设置相同的样式属性时,哪个规则应该"获胜"并应用其样式。
简单来说,特异性就是衡量一个 CSS 选择器有多精确的权重值。特异性高的选择器会覆盖特异性低的选择器,即使特异性低的规则在样式表的后面。
特异性(Specificity)决定了在同一个优先级层级(Cascade Layer 和 Origin)
特异性通常以三位数字(或四位,但通常是三位)来计算,例如 (A, B, C)。比较规则是从左到右:一个 ID ((0, 1, 0)) 永远比 100 个类 ((0, 0, 100)) 更具特异性。
特异性对比示例:
选择器 | 特异性分数 (ID, Class/Pseudo, Element) |
---|---|
h1 |
0, 0, 1 |
.main |
0, 1, 0 |
:where(h1, .main) |
0, 0, 0 |
:is(h1, .main) |
0, 1, 0 (取 .main 的最高特异性) |
!important 的特异性是0,但是!important 优先级位于特异性之上。
但是要避免滥用,因为他会破坏 CSS 级联(Cascade)和特异性(Specificity)的自然流,并且会使得样式难以追溯来源,从而增大调试难度。此外还会限制代码的灵活性和可重用性,别人想要用你的组件,就被迫使用优先级更高的!important来覆盖你的!important。
零特异性的应用场景
零特异性使 :where()
成为定义基础样式 或重置样式的完美工具。
-
基础样式/重置样式 (Baseline Styling): 当你定义一个全局或组件级的默认样式时,你希望它能够被任何其他普通的类选择器轻易覆盖,而不需要引入
!important
或增加额外的特异性。css/* 所有的链接和按钮,默认颜色为蓝色 */ :where(a, button) { color: blue; /* 特异性为 0,0,0 */ } /* 此时,一个简单的类就可以轻松覆盖上面的基础样式 */ .cta-button { color: red; /* 特异性为 0,1,0,轻松获胜 */ }
-
避免特异性膨胀: 如果使用
:is(a, button)
,它的特异性是0,0,1
(取a
或button
的特异性),会增加后续覆盖样式的难度。而:where()
则完全消除了这种顾虑。
这就是为什么很多组件库,例如 antd 会这样用
三、:where()
的其他优势
1. 简化复合选择器
:where()
可以用在选择器链中的任何位置,极大地简化了复杂的嵌套选择器。
简化前:
css
/* 给侧边栏和页脚内的所有链接设置样式 */
.sidebar a,
.footer a {
text-decoration: none;
}
简化后:
css
:where(.sidebar, .footer) a {
text-decoration: none;
}
2. 容错性(Forgiving Parsing)
与传统的逗号分隔选择器列表不同,:where()
(以及 :is()
)接受一个可容错的选择器列表。
-
传统列表: 如果列表中的任何一个选择器是无效的(例如,使用了旧浏览器不支持的最新特性),整个规则块都会被忽略 。
cssh1, :unsupported-selector { /* 如果 :unsupported-selector 无效,h1 的样式也会失效 */ color: red; }
-
:where()
/:is()
列表: 如果列表中的一个选择器是无效的,浏览器只会忽略该无效的选择器 ,而继续使用列表中的其他有效选择器。css:where(h1, :unsupported-selector) { /* 浏览器会忽略 :unsupported-selector,但 h1 的样式仍然有效 */ color: red; }
四、:where()
与 :is()
的选择
特性 | :where(<选择器列表>) |
:is(<选择器列表>) |
---|---|---|
功能 | 选中列表中任一匹配的元素 | 选中列表中任一匹配的元素 |
特异性 | 始终为零 (0,0,0) | 取参数列表中最高特异性 |
主要用途 | 定义基础样式 、重置样式,确保样式易于覆盖。 | 简化选择器语法,同时保留/提升特异性。 |
使用场景:
- 如果你想定义一个容易被覆盖 的默认样式,请使用
:where()
。 - 如果你只是想简化 冗长的选择器,并且希望保持 或提升 特异性(以覆盖其他样式),请使用
:is()
。
五、在 css module 中使用
在 CSS Modules 中使用 :where()
伪类的方式与普通 CSS 基本一致,但需要注意 CSS Modules 的类名哈希化特性对选择器的影响。以下是具体用法和注意事项:
1. 作用于全局选择器(不被哈希化的类名)
如果需要用 :where()
匹配全局样式类 (未被 CSS Modules 哈希化的类名,通常通过 :global()
声明),直接使用即可:
css
/* 组件的 CSS Modules 文件(如 Button.module.css) */
/* 全局类名 .btn-global 不被哈希化 */
:global(.btn-global) {
padding: 8px;
}
/* 用 :where() 聚合全局选择器 */
:where(:global(.btn-global), :global(.link-global)) {
color: blue;
}
编译后,:where()
会保留对全局类名的匹配,不受哈希影响。
2. 作用于本地选择器(被哈希化的类名)
如果 :where()
内部包含本地类名 (会被 CSS Modules 哈希化,如 .btn
编译为 btn__123
),需要注意:
CSS Modules 会对 :where()
内部的本地类名进行哈希化,但 :where()
的特异性依然生效。
示例:
css
/* Button.module.css */
/* 本地类名 .btn 和 .link 会被哈希化 */
.btn {
border: 1px solid #ccc;
}
.link {
text-decoration: none;
}
/* 用 :where() 聚合本地类名 */
:where(.btn, .link) {
font-size: 14px;
}
编译后(假设哈希后缀为 __abc
),实际生成的 CSS 为:
css
.btn__abc {
border: 1px solid #ccc;
}
.link__abc {
text-decoration: none;
}
:where(.btn__abc, .link__abc) {
font-size: 14px;
}
此时,:where(.btn__abc, .link__abc)
会匹配哈希化后的类名,且特异性为 0。
3. 混合全局和本地选择器
:where()
中可以同时包含全局和本地选择器,CSS Modules 只会哈希化本地类名:
css
/* Button.module.css */
.localClass {
margin: 0;
}
/* 混合全局和本地选择器 */
:where(.localClass, :global(.globalClass)) {
padding: 0;
}
编译后:
css
.localClass__def {
margin: 0;
}
:where(.localClass__def, .globalClass) {
padding: 0;
}
完~~