学完这篇你能得到一套可直接套用的 CSS 选择器权重计算法,以及让样式表不越写越乱的实操习惯。
第一步:记住四元组 (a, b, c, d)
CSS 优先级本质是一个四位计数,从左到右逐位比较,高位大的直接赢,不用看后面:
- a —— 内联样式,写在标签
style="" 里的算 1
- b —— ID 选择器,如
#header
- c —— 类
.btn、属性 [type="text"]、伪类 :hover / :nth-child() / :focus-visible
- d —— 元素
div、伪元素 ::before / ::after
看两个例子:
#nav .item a:hover { } /* (0,1,2,1) */
ul li.active { } /* (0,0,1,2) */
前者只有 b 位是 1,后者是 0,所以前者赢,后面几位不用比。
注意:*、组合符 > + ~、后代选择器的空格,全都不计入权重。写 div > p 和写 p 的权重是一样的 (0,0,0,1)。很多人靠堆层级来"提权",其实白写。
第二步:在浏览器里验证,别靠脑算
打开 Chrome / Edge 开发者工具(F12),切到 Elements → Styles 面板,鼠标悬停在任意一条规则的选择器上,会浮出 specificity: (0, 2, 1) 这样的提示。被划掉的属性就是被更高权重的规则打败的。
这是排查"样式为什么不生效"最快的路径:先看目标属性有没有被划掉,被划掉就往下找那条赢了的规则,直接对比权重。
第三步:处理三个特殊角色
:where()、:is()、:not()、:has() 的权重规则和普通伪类不同:
:where(...) 的权重恒为 0,括号里写多复杂都不算
:is(...) :not(...) :has(...) 取括号内参数里最高的那个
- 伪元素
::before 计入 d 位
:is(#a, .b) p { } /* (0,1,0,1),取 #a 的 1 个 ID */
:where(#a, .b) p { } /* (0,0,0,1),归零 */
注意:内联 style="" 的 a 位只有 !important 能压过去,普通选择器权重堆到天上也没用。
第四步:分清「优先级」和「层叠顺序」
这是最容易混的一步。CSS 的层叠判定分三层,优先级只是其中一层:
- 来源与重要性:
!important 用户样式 > !important 作者样式 > 作者样式 > 用户样式 > 浏览器默认样式
- 选择器权重:即上面的四元组
- 书写顺序:权重完全相同时,后写的赢
另外还有 @layer 层叠层,它插在"来源"和"权重"之间:
@layer base, components, utilities;
@layer components { .btn { color: red } }
.btn { color: blue } /* 未分层的样式赢 */
注意:未分层(unlayered)的样式永远压过分层样式,哪怕层里的选择器是 #id。所以把第三方库、重置样式统统塞进 @layer base,你后面写的组件样式天然就能覆盖它们,不用再拼权重。层里的 !important 会反转层顺序,这个特性别乱用。
第五步:六条最佳实践
- 权重扁平化:整体控制在 (0,1,x) 以内,绝大多数样式用单个类就能选中
- 样式钩子不用 ID:ID 留给 JS 的
getElementById 和页面锚点,一旦用 ID 写样式,后面全得靠 !important 追
!important 当补丁 = 技术债:能用层或命名空间解决的,绝不加 !important
- 覆盖第三方样式用
:where():写成 :where(.第三方类) .我的类,你的权重就固定在 (0,1,0)
- 分层管理:
@layer reset, vendor, base, components, utilities; 一行声明顺序,后面顺序固定
- 命名规范化:BEM(
.block__element--modifier)保证每个类都是同级权重,谁覆盖谁只取决于书写顺序,可预测
第六步:拿一段代码自测
a { color: black } /* (0,0,0,1) */
.nav a { color: red } /* (0,0,1,1) */
.nav .list a { color: blue } /* (0,0,2,1) */
.nav .list a:hover { color: green } /* (0,0,3,1) */
四个规则作用在同一条链接上时:不悬停是 blue,悬停是 green。如果想让 a 那条赢,只能靠 !important 或改成 :where() 结构——这就是为什么一开始就别让权重失控。
小结
- 优先级是四元组 (a 内联, b ID, c 类/属性/伪类, d 元素/伪元素),从左往右比
*、组合符、空格组合器不计数;:where() 权重为 0,:is() / :not() 取最高
- 优先级只是层叠三层里的中间一层,前面还有来源与重要性,后面还有书写顺序
- 未分层样式压过分层样式,用
@layer 一键压制第三方库
- 实践准则:权重扁平、不用 ID 做样式钩子、慎用
!important、命名规范