Skip to main content

CSS 同一个属性有多条规则命中按什么排序?

· 5 min read

核心观点:CSS 级联按"属性"算胜负,不是按"类名"合并。

  1. 裁决顺序:specificity → layer → 源码顺序,最后登场者赢
  2. 冲突点:同一属性只能保留一个值,类名再多也是同一个声明位
  3. 典型坑:layout.css 后加载的 .flex-col 会把 utils.css 的 .lg:flex-row 改回去
  4. 回答"flex-col flex 能不能拆开":不能,CSS 不按类名分摊属性
  5. 推论:抢不同属性时互不干扰,按 reset/utilities/components 拆文件完全可行
  6. 排查路径:DevTools → Computed → 看属性右边列出哪些规则在抢

先抛结论

CSS 不是按"类名维度"做增量合并的,是按"最终属性"算级联。

给一个元素写:

<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">

在 lg 断点(≥1024px)下,至少两个规则同时匹配它:

  • .flex-colflex-direction: column
  • .lg:flex-rowflex-direction: row

两个规则修改的都是 flex-direction,specificity 又相同(都是单 class),所以胜负看源码顺序——谁后写谁赢。

级联的三道关

CSS Cascading Level 5 规范,胜者按这个顺序判定:

  1. Origin & Importance:用户代理 / 用户 / 作者样式,以及 !important
  2. Context@layer 定义的级联层
  3. Element-Attachedstyle=""
  4. Specificity:ID > class > tag
  5. 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 面板里划线的就是被覆盖的。

为什么 flexflex-col 不能拆开

不行。flex-direction 一个元素上只能有一个值。CSS 的合并单位是属性,不是类名——浏览器不会按"哪个 class 给了什么"分摊合并。

很多人以为这样:

<div class="flex flex-col"> <!-- flex 给 display,flex-col 给 direction,各自贡献一部分 -->

实际不是。.flex 里压根没 flex-direction 这条声明——它只贡献 display: flex。两条类打的是不同属性(display vs flex-direction),不是同一属性的两次设值。

走一遍浏览器实际做的:

  1. 解析 <div class="flex flex-col">,收集命中规则
  2. display:候选池 = [flex].flex 胜出
  3. flex-direction:候选池 = [column].flex-col 胜出(.flex 没设这条属性,不在候选池)
  4. 合并进 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: columnborder-radius: 8pxbackground: 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

  1. CSS Cascading and Inheritance Level 5 —— W3C
  2. Tailwind CSS: Adding Custom Styles —— Tailwind 官方