配了八年 CSS,每次样式冲突我都要靠 !important 续命——今天 @layer 把这件事彻底变了

我管一个二三十人的前端团队,每次 Code Review 最怕看到两样东西:!importantz-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 层的任意样式都会覆盖 componentsbasereset 三层——不管选择器多简单,不管你写了多少个 !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 层,你的代码在 componentsutilities 层,永远不会被它们的选择器权重意外覆盖。


浏览器支持:已经是 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 硬编码的颜色在哪里。


下一步你可以做的

  1. 现在就在你的 CSS 文件顶部加上层声明,哪怕只有 @layer base, components, utilities; 三行,它会让整个样式体系的优先级变得可预测
  2. @import layer(vendors) 引入所有第三方 CSS,彻底隔离外部样式渗透
  3. 删掉所有非必要的 !important,用层优先级替代它——大多数情况下,你只需要把样式移到一个更「高层」的层里就行

@layer 不是新东西,2022 年 Chrome 99 就支持了。但它真正被大规模采用,还是 2024 年 Baseline 全面落地之后的事。如果你还在用 !importantz-index 大数字解决样式冲突,是时候把这个习惯换掉了。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 663 阅读