CSS 权重怎么算:specificity、:is()、:where() 与级联顺序
CSS 样式覆盖不只看谁写在后面。本文用 A-B-C 三元组讲清选择器权重、现代伪类、!important、cascade layer 和实际排查流程。
打开配套工具 →改一行颜色却没有生效,很多人第一反应是继续加一层选择器,或者直接写 !important。短期看见效果,半年后同一个组件可能已经叠到 .page #app .content .card.is-active:hover,再也没人敢动。
先算清选择器权重,通常比继续堆代码快。CSS 选择器权重计算器可以批量计算 A-B-C 三段,并正确处理 :is()、:not()、:has() 和 :where()。它适合审计和比较;最终为什么胜出,仍要回到完整级联判断。
A-B-C 不是一个十进制分数
普通选择器的 specificity 可以写成三元组:
- A:ID 选择器,例如
#checkout; - B:类、属性和伪类,例如
.button、[disabled]、:hover; - C:元素和伪元素,例如
button、h2、::before。
例如:
#checkout .button.primary:hover它包含一个 ID、三个 B 类项目、没有元素,结果是 (1,3,0)。
.card[data-state="open"] > h2::before它是 (0,2,2)。
比较时从左到右逐段看。(1,0,0) 一定高于 (0,99,99),因为第一段已经分出胜负。把它写成“100 分、10 分、1 分”只适合非常粗略的入门解释,一旦数量变大就会产生错误结论。
通配符、组合符和 :where()
空格、>、+、~ 等组合符不增加权重,通配符 * 也是零。
:where() 更特别:它本身和括号内全部参数都贡献零权重。
:where(.article, #legacy) h2尽管参数里出现了 ID,最终只有 h2 的 (0,0,1)。这让组件库可以提供结构完整但容易覆盖的默认样式。例如:
:where(.prose) :where(h2, h3) {
margin-block: 1.5em 0.5em;
}业务页面用一个普通类就能覆盖,不需要知道组件库内部嵌套了几层。
:is()、:not() 和 :has() 看最高参数
这三个伪类不会像普通伪类那样简单加一。它们采用参数列表中权重最高的选择器。
.button:is(:hover, #forced):is() 的参数中 #forced 最高,因此整体会包含一个 ID。即使当前元素实际匹配的是 :hover,计算权重时仍看整个参数列表的最高值。
:not() 和 :has() 同理:
.card:has(> img.hero)
.notice:not(#legacy)把 ID 放进这些参数,经常会无意间抬高整条规则。审计旧项目时,应特别搜索 :is(、:not(、:has(,不要只数井号和点号。
:nth-child() 也有一个容易漏掉的分支:
li:nth-child(2n of .featured, #pinned)伪类本身计入 B,of 后面再按最高参数计算。
内联样式、!important 和 layer 是另一层问题
A-B-C 只描述普通选择器。浏览器决定最终声明时,还会依次考虑来源、重要性、级联层、作用域、权重和出现顺序。
!important 不是“再加一万分”。重要声明会进入另一组比较,同组中才继续看来源与权重。滥用它的麻烦不只是难覆盖,还会让后续调试者误以为选择器算错了。
@layer 可以在不堆权重的情况下管理来源:
@layer reset, components, utilities;
@layer components {
.button { color: navy; }
}
@layer utilities {
.text-red { color: red; }
}同为普通作者样式时,后声明的 layer 优先级更高。即使组件选择器看起来更长,也可能被 utilities 层覆盖。排查时必须看 DevTools 标出的 layer,而不是只比较三元组。
同权重时为什么后写的生效
如果来源、重要性、层和权重都相同,后出现的声明通常胜出:
.card { color: blue; }
.card { color: green; }第二条生效。但“后出现”是浏览器最终加载顺序,不一定是你正在看的源文件顺序。打包器、CSS Modules、按路由加载、异步样式和第三方插件都可能改变产物顺序。
因此开发环境正常、生产环境失效时,要检查生产构建后的 CSS 和 Network 加载顺序,不能只看 Sass 文件。
一套更快的排查方法
在浏览器 DevTools 选中元素,先看 Computed 面板最终值,再回到 Styles 面板找被划掉的声明。确认双方是否都满足媒体查询、容器查询和状态条件。
复制竞争的两条选择器,用权重工具直接比较。如果权重相同,再看 layer 和源码顺序;如果权重不同,找出是哪一个 ID、属性或伪类把差距拉开。
再用CSS Selector / XPath 测试器配合实际 HTML 检查选择器是否命中过多、依赖不稳定层级,或根本没有匹配目标元素。
最后再改代码。常见的健康做法是:
- 去掉不必要的父级和 ID;
- 用组件类表达状态,而不是依赖页面深层结构;
- 用
:where()包住只为限定范围、不希望增加权重的部分; - 用 layer 管理 reset、组件和工具类;
- 只在真正需要覆盖第三方重要声明时考虑
!important,并写清原因。
总结
CSS 权重没有神秘公式,它是按位比较的 A-B-C 三元组。真正容易出错的是现代伪类、级联层、构建顺序和 !important 被混在一起讨论。
先把选择器本身算清,再检查 layer、来源和加载顺序。这样解决的是级联结构,而不是给下一位维护者留下一条更长的选择器。