CSS 同一个属性有多条规则命中按什么排序?
核心观点:CSS 级联按"属性"算胜负,不是按"类名"合并。
- 裁决顺序:specificity → layer → 源码顺序,最后登场者赢
- 冲突点:同一属性只能保留一个值,类名再多也是同一个声明位
- 典型坑:layout.css 后加载的
.flex-col会把 utils.css 的.lg:flex-row改回去 - 回答"flex-col flex 能不能拆开":不能,CSS 不按类名分摊属性
- 推论:抢不同属性时互不干扰,按 reset/utilities/components 拆文件完全可行
- 排查路径:DevTools → Computed → 看属性右边列出哪些规则在抢
先抛结论
CSS 不是按"类名维度"做增量合并的,是按"最终属性"算级联。
给一个元素写:
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
在 lg 断点(≥1024px)下,至少两个规则同时匹配它:
.flex-col→flex-direction: column.lg:flex-row→flex-direction: row
两个规则修改的都是 flex-direction,specificity 又相同(都是单 class),所以胜负看源码顺序——谁后写谁赢。
级联的三道关
按 CSS Cascading Level 5 规范,胜者按这个顺序判定:
- Origin & Importance:用户代理 / 用户 / 作者样式,以及
!important - Context:
@layer定义的级联层 - Element-Attached:
style="" - Specificity:ID > class > tag
- Order of Appearance:源码顺序,后者胜出
Tailwind 写出来的 utility class,前 3 项几乎一致,specificity 也基本相同,所以最终裁决永远是源码顺序。
实战:.lg:flex-row 为什么没生效
两个 CSS 文件按下面顺序加载:
/* utils.css 先加载 */
.flex-col { flex-direction: column; }
@media (min-width: 64rem) {
.lg\:flex-row { flex-direction: row; }
}
/* layout.css 后加载 */
.flex-col { flex-direction: column; }
打开 lg 屏看 DevTools:
.lg\:flex-row命中 ✓.flex-col也命中 ✓- 最终
flex-direction: column
layout.css 里压根没写 .lg:flex-row,但它重新声明了一次 .flex-col——flex-direction 这个属性就被改回去了。
排查路径:DevTools → Computed → 看 flex-direction 右边列出哪些规则在抢这个属性 → Styles 面板里划线的就是被覆盖的。
为什么 flex 和 flex-col 不能拆开
不行。flex-direction 一个元素上只能有一个值。CSS 的合并单位是属性,不是类名——浏览器不会按"哪个 class 给了什么"分摊合并。
很多人以为这样:
<div class="flex flex-col"> <!-- flex 给 display,flex-col 给 direction,各自贡献一部分 -->
实际不是。.flex 里压根没 flex-direction 这条声明——它只贡献 display: flex。两条类打的是不同属性(display vs flex-direction),不是同一属性的两次设值。
走一遍浏览器实际做的:
- 解析
<div class="flex flex-col">,收集命中规则 - 对
display:候选池 =[flex],.flex胜出 - 对
flex-direction:候选池 =[column],.flex-col胜出(.flex没设这条属性,不在候选池) - 合并进 computed style:
display: flex; flex-direction: column
flex 写前面还是后面无所谓——它压根没参与 flex-direction 这一轮。
真正的"拆不开"是同一属性被两个类同时设值。<div class="bg-red-500 bg-blue-500"> 的 background-color 候选池是 [red, blue],cascade 只挑一个,不会是红蓝渐变——因为一 个属性一个元素只能有一个值。
抢不同属性:互不干扰
上一节说的是"抢同一个属性才会互斥"——反过来,两份 CSS 抢的是不同属性,就互不干扰,都生效:
/* utils.css */
.flex-col { flex-direction: column; }
.rounded { border-radius: 8px; }
/* layout.css */
.bg-white { background: white; }
同一个 <div class="flex-col rounded bg-white">,三个类各管各的属性——flex-direction: column、border-radius: 8px、background: white 一并进 computed style,一条都不丢。
所以按"功能维度"拆 CSS 文件(reset / utilities / components)完全可行:reset 管 * 通配属性、utilities 管 display / color / padding 这类工具属性、components 管业务属性——三层各管各的属性域,没人挡别人的路。只要不同文件的规则不抢同一个属性,文件怎么拆都安全。
工程上的几种应对
别在 layout / component 里重新声明 utility
/* ❌ 别这样写 */
.card { display: flex; flex-direction: column; }
要竖排就直接 <div class="flex flex-col">,别用 .card 自己再包一层。
用 @layer 把 utility 锁在最后
@layer base, components, utilities;
@layer components {
.card { /* ... */ }
}
@layer utilities {
/* Tailwind 生成的 utility class */
}
utilities 层最后定义,同 specificity 下永远胜出 components。
同一 @layer 内的 utility 还是按源码顺序比,跨层才按 layer 顺序比。@layer 不是万能的,得自己安排好加载顺序。
提高 specificity(仅调试用)
.lg\:flex-row.force-row { flex-direction: row !important; }
!important 会直接跳过整个级联,长期用会让样式变得不可调试。
References
- CSS Cascading and Inheritance Level 5 —— W3C
- Tailwind CSS: Adding Custom Styles —— Tailwind 官方