配了八年 CSS,每次样式冲突我都要靠 !important 续命——今天 @layer 把这件事彻底变了
我管一个二三十人的前端团队,每次 Code Review 最怕看到两样东西:!important 和 z-index: 999999。不是这些东西本身有多可怕,是它们的出现往往说明——样式优先级已经乱成一锅粥了。
第三方 UI 库要覆盖、多个 design token 要合并、设计师给的「最后再加一条」永远会覆盖你辛苦配的全局样式……这些问题,CSS 给你憋了八年,今天 @layer 终于把它说清楚了。
核心规则:谁后声明,谁优先级高
@layer 的本质是把 CSS 规则分进不同的「层」,然后按层决定优先级——不是按选择器权重,不是按书写顺序,是按层本身。
/* 第一步:声明层的顺序,越靠前优先级越低 */
@layer reset, base, components, utilities;
/* 第二步:在对应层里写样式 */
@layer reset {
* { margin: 0; padding: 0; box-sizing: border-box; }
}
@layer base {
button { padding: 8px 16px; border: 1px solid #ccc; }
}
@layer components {
.btn-primary { background: #1677ff; color: #fff; }
}
@layer utilities {
.mt-4 { margin-top: 16px; }
}
上面这段代码,utilities 层的任意样式都会覆盖 components、base、reset 三层——不管选择器多简单,不管你写了多少个 !important。这是 @layer 最值钱的地方:把优先级的复杂度从「谁覆盖谁算不清」变成「谁在哪个层说清楚」。
!important 的优先级被反转了——这件事很多人不知道
@layer 有一个反直觉的特性:在层内用 !important 会让优先级反过来。
正常情况下(没有 @layer 时),!important 声明的样式会覆盖普通声明。但在 @layer 的世界里:
- 普通声明:层越后 → 优先级越高
!important声明:层越前 → 优先级越高(完全反转)
@layer A, B;
@layer A {
p { color: red !important; } /* !important 时,A层优先级反而最低 */
}
@layer B {
p { color: blue; } /* 普通声明时,B层优先级最高 */
}
/* 最终 p 是 blue,因为 B层普通声明 > A层 !important 声明 */
这个机制是设计出来的,用来解决「第三方库用 !important 硬编码了样式,你却想用自己的覆盖它」的问题——只要把你的样式放进更早的层并加 !important,就能把第三方库的 !important 给压下去。
匿名层:不想取名时的临时方案
给层命名有时候很麻烦,尤其是只想临时把某段样式隔离开的场景。@layer 支持匿名层:
@layer { /* 这是匿名层,按书写顺序决定优先级 */
.temp-fix { display: none; }
}
@layer {
.another { opacity: 0; }
}
/* 第二个匿名层优先级更高,会覆盖第一个匿名层的 .temp-fix */
匿名层的缺点是没法从外部引用,无法和其他层声明优先级关系。但它的好处是:你不需要想名字,适合临时调试或者一次性样式隔离。
@import 也能进层:第三方 CSS 的隔离方案
这是 @layer 最被低估的用法。你引入第三方 CSS 时,可以用 @import layer() 直接把它扔进一个指定的层:
/* 把 Bootstrap 全部扔进 vendors 层,优先级最低 */
@import url("bootstrap.min.css") layer(vendors);
/* 然后在自己的层里正常写,覆盖它不需要任何技巧 */
@layer vendors, base, components;
@layer components {
.btn { border-radius: 4px; } /* 直接覆盖 Bootstrap 的原始样式 */
}
这意味着什么?你引入任何第三方库,都不需要再担心它的样式渗透进你的代码。Bootstrap、Ant Design、Tailwind——全部扔进 vendors 层,你的代码在 components、utilities 层,永远不会被它们的选择器权重意外覆盖。
浏览器支持:已经是 Baseline 2022,放心用
| 浏览器 | 首个完整支持版本 |
|---|---|
| Chrome | 99 |
| Edge | 99 |
| Firefox | 97 |
| Safari | 15.4 |
| Opera | 86 |
Chrome 99 就支持了,这意味着 2022 年之后的 Chrome、Edge、Firefox、Safari 全部支持。不需要 polyfill,不需要降级方案,直接用就行。
实际项目怎么落地
我团队现在的 CSS 架构是这样的:
/* 第一步:声明完整层顺序,一次性说清楚优先级 */
@layer reset, tokens, base, components, patterns, utilities, overrides;
/* 第二步:把第三方库扔进最底层 */
@import url("antd.min.css") layer(reset);
@import url("normalize.css") layer(reset);
/* 第三步:design tokens */
@layer tokens {
:root {
--color-primary: #1677ff;
--spacing-unit: 8px;
}
}
/* 第四步:业务组件,永远不会被第三方库干扰 */
@layer components {
.my-button { background: var(--color-primary); }
}
这个结构的好处是:你永远不需要写 !important 来覆盖第三方样式。当设计师说「按钮颜色要换成品牌色」时,你只需要改一行 --color-primary,组件层自动生效,不用再去找那个被第三方库 !important 硬编码的颜色在哪里。
下一步你可以做的
- 现在就在你的 CSS 文件顶部加上层声明,哪怕只有
@layer base, components, utilities;三行,它会让整个样式体系的优先级变得可预测 - 把
@import layer(vendors)引入所有第三方 CSS,彻底隔离外部样式渗透 - 删掉所有非必要的
!important,用层优先级替代它——大多数情况下,你只需要把样式移到一个更「高层」的层里就行
@layer 不是新东西,2022 年 Chrome 99 就支持了。但它真正被大规模采用,还是 2024 年 Baseline 全面落地之后的事。如果你还在用 !important 和 z-index 大数字解决样式冲突,是时候把这个习惯换掉了。
评论区
登录后可评论。