编程 CSS @layer 实战笔记:用层序终结选择器特异性战争

2026-08-31 00:02:52

CSS @layer 实战笔记:层序才是优先级

先看一个让人血压升高的场景

项目接了个第三方 UI 库,需求是把默认按钮改成 12px 字重 700。打开 DevTools 一看,框架自带 .btn-primary .btn-icon 这种 0-3-1 的选择器,你想覆盖它,得写出:

body .app .content .btn-primary .btn-icon {
  font-size: 12px;
  font-weight: 700;
}

这还没完,下个迭代又来了个全局 reset 把 button { font-sizer: inherit } 横插一脚。于是你的样式表里到处都是 !important 和冗长选择器,每次改样式都要开一场「谁更长谁赢」的军备竞赛。

@layer 的思路:别比兵器了,直接排座次

级联层让优先级跟「层序」绑定,跟选择器特异性脱钩。你把第三方库塞进 layer(vendor),业务样式放普通未分层,就能用 .btn 平推掉框架里 0-3-1 的规则。

@layer reset, vendor, base, components, utilities;

@layer vendor {
  .btn-primary .btn-icon { font-size: 14px; }
}

@layer components {
  .btn { font-size: 12px; }
}

不写任何 !important.btn 赢了。因为 components 写在 vendor 后面。

层序规则就这几条

  1. 前面的层低,后面的层高。在样式表最上面用 @layer 声明一次层序,后面各文件里 @layer components { ... } 追加内容,座次早已锁定。
  2. 层之间完全无视特异性.btn 就是能赢 #main .btn-primary .btn-icon,只要前者所在的层靠后。
  3. 层内该比特异性还是比,逃不掉。
  4. 未分层样式(普通写在全局的规则)在普通声明中赢过所有分层样式,也是无视特异性。想在关键时刻强制覆盖,直接把规则写成未分层即可,全程不用 !important
/* 未分层 */
.btn { color: red; }

@layer vendor {
  body .btn { color: blue; }  /* 层内特异性再高也输 */
}
/* 最终 red,因为是未分层。 */
  1. !important 层序反转。在 !important 声明里,先声明的层最猛。分层 !important 还压过未分层的 !important
@layer low, high;

@layer low {
  .foo { color: red !important; }
}
@layer high {
  .foo { color: blue !important; }
}
/* 结果是 red,因为 low 层声明在前,!important 下低层反而强。 */

这玩意平时真的用不上,但知道它存在,排查问题时少掉一半头发。

嵌套层和导入分层

嵌套层适合组件内部自己分小层:

@layer card {
  @layer header, body, footer;  /* 声明子层顺序 */
  @layer body { ... }
}
/* 或者用点号 */
@layer card.body { ... }

第三方库直接用 @import 分层塞进低层:

@import url("lib.css") layer(vendor);

注意 @import 必须放在样式表最前面,只能排在 @charset 之后、纯声明层序的 @layer 之后。前面一旦出现普通规则,后面的 @import 直接忽略,而且浏览器无警告。

revert-layer:撤销本层的设定

某属性想回到上一层叠层的值,用 revert-layer

@layer base {
  .btn { border: 1px solid gray; }
}

@layer components {
  .btn { border: 1px solid gray; border-radius: 999px; }
  .btn.ghost { border-radius: revert-layer; }  /* 回到 base 的值 */
}

别指望 @layer 解决两件事

作用域问题它管不了。 card.css 里写 @layer card { } 不表示选择器只在 .card 内生效,层级跟作用域没关系。

层序在第一次出现时即锁定。 你这文件里写 @layer a, b; 另一个文件开头写 @layer b, a; 没用,真正生效的是最先解析到的那个声明顺序。所以层序声明集中放入口文件最顶端,别零散分布。

分层 CSS 不会替换非分层 CSS。 你有重置需求,reset.css 可以当未分层放最底下做基线,再把分层叠加在它上面。

推荐落地层序

@layer reset, tokens, base, layout, components, utilities;

这个顺序的爽点:

  • utilities 天然压过 components
  • components 压过 base
  • 第三方 reset 永远在最底,不用担心它哪天反扑
  • 临时加样式写成未分层,直接最高优先

从 Baseline 2022(2022 年 3 月)开始,这东西在主流浏览器里都能用。我的建议:新项目直接用 @layer 搭骨架,老项目先在「新增模块」或「覆盖第三方样式」的场合单独用,不必等全站改造完。

复制全文 生成海报 Cascade Layers @layer CSS 前端 样式优先级

推荐文章

程序员茄子在线接单