大型项目里同时引入多个样式源是常态——一个第三方组件库、一套内部设计系统、再加上各业务模块自己的样式。这些来源的 CSS 混在同一个优先级体系里,谁覆盖谁完全取决于选择器权重的博弈。时间长了,!important 满天飞,开发人员不敢删旧样式,只能一层层往上叠。CSS 级联层在 2022 年进入标准,到 2025 年主流浏览器已经全面支持。它让你可以显式地为不同来源的样式分层,层的优先级由你定义,层内的选择器权重不会跨层比较。这篇文章用一个集成三个样式源的完整案例,把 @layer 从语法到架构都讲清楚。
一、问题的根源:所有样式在一个平面里竞争
传统 CSS 的优先级体系是二维的:来源(用户样式、浏览器默认样式、开发者样式)加上选择器权重(行内样式、ID、类、标签)。所有开发者写的样式都在同一个平面里,按权重和书写顺序决定最后谁生效。
当项目里只有一套样式文件时,这个机制勉强够用。但一旦引入第三方组件库,局面就变了。组件库的选择器权重你控制不了,它可能用了一个 .btn-primary,你可能也用了同一个类名。谁覆盖谁取决于加载顺序和选择器复杂度。如果组件库的样式写在后面,你的自定义样式就会被覆盖。为了解决这个问题,开发者被迫使用更高权重的选择器,或者直接上 !important。
更麻烦的是,不同团队的样式之间没有明显的边界。A 团队写的样式可能不小心覆盖了 B 团队的组件,排查起来极其耗时。级联层就是专门来解决这个问题的:它给 CSS 加了一个”纵向维度”——层与层之间的优先级你说了算,同一层内部才按传统权重比较。
二、@layer 的核心语法
级联层的语法分两部分:声明层和给层分配样式。
2.1 声明层的顺序
你需要在所有样式之前声明层以及它们的优先级顺序:
/* 层声明:越靠后的层优先级越高 */
@layer base, components, utilities;
在这个声明里,utilities 层的优先级最高,base 层最低,components 在中间。顺序很重要——它决定了跨层时的优先级关系,和 CSS 本身的书写顺序无关。即使后面 base 层的代码写在了文件末尾,它的优先级仍然低于 components 层。
匿名层也可以直接写样式:
@layer {
/* 这是一个匿名层,优先级低于所有命名层 */
}
没有放在任何层里的样式,相当于在一个比所有命名层更靠后的隐含层里,优先级最高。这个设计保证了向后兼容——老项目里没有分层代码的样式,天然处于最高优先级。
2.2 把样式放进层里
放进层有两种写法。第一种是嵌套块:
@layer base {
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: sans-serif;
}
}
@layer components {
.card {
border-radius: 8px;
padding: 16px;
}
}
@layer utilities {
.hidden {
display: none;
}
}
第二种是使用 @import 直接把外部文件注入到某个层:
/* 将第三方组件库的样式文件放到 vendor 层 */
@import url('bootstrap.css') layer(vendor);
/* 内部设计系统放到 design 层 */
@import url('design-system.css') layer(design);
这种写法特别适合管理第三方 CSS 文件——你不需要修改它们的源码,只需要在 @import 时指定一个层,整份文件的样式就被整体压入那个层里。层的优先级你通过声明顺序来控制。
三、实战案例:一个项目集成三个样式源
假设我们正在做一个后台管理系统,样式来源有三个:
- Normalize.css——浏览器默认样式重置,最低优先级。
- 内部 UI 组件库(
ui-lib.css)——团队自己维护的按钮、表格、表单组件,优先级居中。 - 页面业务样式——各个页面特有的自定义覆盖,优先级最高。
在引入级联层之前,这三个来源的样式混在一起,业务样式要覆盖组件库样式就得靠选择器权重或者 !important 硬抬。现在用 @layer 来重新组织。
3.1 声明层并导入外部样式
/* 1. 声明层的优先级顺序:靠后的层优先级更高 */
@layer reset, ui-lib, pages;
/* 2. 将第三方 CSS 注入到对应的层 */
@import url('normalize.css') layer(reset);
@import url('ui-lib.css') layer(ui-lib);
/* 3. pages 层的样式直接内联写 */
@layer pages {
/* 业务页面样式放在这里 */
}
现在 pages 层的优先级最高,ui-lib 层次之,reset 层最低。无论 normalize.css 和 ui-lib.css 里写了什么选择器,只要你的业务样式放在 pages 层里,它就能覆盖前两者——不需要提高选择器权重,不需要 !important。
3.2 ui-lib.css 的示例内容
内部组件库定义了一些基础组件样式:
/* ui-lib.css —— 自动被注入到 ui-lib 层 */
.button {
display: inline-flex;
align-items: center;
padding: 10px 20px;
border: none;
border-radius: 6px;
background-color: #3b82f6;
color: #fff;
font-size: 14px;
cursor: pointer;
}
.button:hover {
background-color: #2563eb;
}
.table {
width: 100%;
border-collapse: collapse;
}
.table th,
.table td {
padding: 12px 16px;
border-bottom: 1px solid #e5e7eb;
text-align: left;
}
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: #fff;
border-radius: 12px;
padding: 24px;
min-width: 400px;
}
3.3 业务页面样式——轻松覆盖组件库
在具体页面上,产品经理要求按钮改成红色、表格去掉边框线、弹窗加一个阴影。以前你需要确保自己的选择器权重更高,现在只需要把样式放在 pages 层里:
@layer pages {
/* 按钮变红色,同样的 .button 选择器也能覆盖 ui-lib 层 */
.button {
background-color: #ef4444;
font-weight: 600;
}
.button:hover {
background-color: #dc2626;
}
/* 表格去掉边框,改成纯色背景 */
.table th,
.table td {
border-bottom: none;
}
.table th {
background-color: #f8fafc;
color: #475569;
}
/* 弹窗加阴影和动画 */
.modal-content {
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
/* 特殊页面还可以定义自己的工具类 */
.dashboard-stat {
font-size: 2rem;
font-weight: 700;
color: #1e293b;
}
}
注意这里的 .button 选择器和 ui-lib.css 里的一模一样。如果在同一层里,后面的会覆盖前面的。但现在它们在两个层中,pages 层在声明时排在 ui-lib 层之后,所以 pages 层的 .button 直接生效——和选择器权重无关,和书写顺序无关,只和层的声明顺序有关。
四、嵌套层:精细化控制
层内还可以再分层。比如 ui-lib 内部可以拆分为基础变量和具体组件:
@layer ui-lib {
@layer variables {
:root {
--primary: #3b82f6;
--radius: 6px;
}
}
@layer components {
.button {
background-color: var(--primary);
border-radius: var(--radius);
}
}
}
嵌套层的完整名称用点号连接:ui-lib.variables。在声明优先级时可以精确到嵌套层:
@layer ui-lib.variables, ui-lib.components;
这样在同一个 ui-lib 大层里,components 的优先级高于 variables。嵌套层让大型设计系统内部也能保持清晰的层级关系。
五、!important 在层级联层里的行为
!important 在级联层里有一个反直觉的行为:它反转了层级关系。
在非分层样式里,!important 只是提高当前规则的优先级。但在分层环境中,低优先级层的 !important 会覆盖高优先级层的 !important。这个设计是为了让基础层(如 reset)在标记了 !important 时仍然能保持一定程度的权威——比如 reset 里强制所有元素 box-sizing: border-box !important,高优先级的业务样式如果不加 !important 是无法覆盖它的。
@layer base {
* {
box-sizing: border-box !important;
}
}
@layer pages {
.widget {
box-sizing: content-box; /* 这行不会生效,因为 base 层的 !important 优先级高 */
box-sizing: content-box !important; /* 这行才生效 */
}
}
在日常开发中这个细节不需要刻意记忆,只需要知道:如果低层样式用了 !important,高层样式想覆盖的话也必须用 !important。最好的做法仍然是尽量不用 !important,让层的声明顺序来控制优先级。
六、浏览器支持情况
CSS 级联层已经是非常成熟的特性,支持矩阵很干净:
- Chrome 99+(2022年3月)
- Edge 99+
- Safari 15.4+(2022年3月)
- Firefox 97+(2022年2月)
- 所有主流移动浏览器同步支持
截至 2025 年 7 月,全球覆盖率已经超过 96%。如果你的项目不再需要支持 IE 或者 2021 年以前的浏览器版本,可以直接在生产环境使用,不需要任何 polyfill。不支持级联层的浏览器会忽略 @layer 声明,所有样式回到传统的扁平优先级体系——功能不坏,只是失去了分层管理的能力。这个降级行为是设计好的,不会导致样式崩溃。
七、从旧项目迁移的务实步骤
不要一上来就把整个项目的 CSS 全拆成层。推荐分三步走:
第一步:从第三方库开始。用 @import url(...) layer(vendor) 把现有的第三方 CSS 文件包进一个低优先级的层。这是性价比最高的一步——你不需要改任何业务代码,第三方样式就被整体降权了。原来因为加载顺序导致第三方样式覆盖你自定义样式的问题,立刻消失。
/* 原来:第三方样式可能覆盖你的自定义样式 */
/* <link rel="stylesheet" href="bootstrap.css" rel="external nofollow" > */
/* <link rel="stylesheet" href="custom.css" rel="external nofollow" > */
/* 现在:把 Bootstrap 压入 vendor 层,自定义样式保持在高优先级 */
@import url('bootstrap.css') layer(vendor);
第二步:抽离基础样式。把 reset.css、normalize.css、CSS 变量定义、全局排版规则等基础样式放进 @layer base 里。这些样式本来就不应该被业务代码”覆盖”——它们的作用是为整个页面提供基线。
第三步:定义团队层的约定。和团队商定一个分层策略,比如 @layer base, components, pages, overrides。以后新增样式必须在对应层里写,code review 时也能一眼看出某段样式属于哪个层级。
八、总结
级联层的价值不在于让 CSS 多了一种写法,而在于它解决了一个架构层面的问题:不同模块、不同团队、不同来源的样式,终于可以有自己的”优先级段位”了。你不用再担心第三方组件库的选择器比你的高,也不用靠 !important 硬撑。层的优先级在声明时就定下来了,和选择器怎么写无关。
对于正在维护大型项目的团队来说,级联层可能是近年来最实用的 CSS 新特性之一。它的学习成本极低——核心语法就 @layer 一个关键字——但能带来工程上的长期收益。文中所有代码都是独立的,你可以直接在现有项目里试验:挑一个第三方 CSS 文件,用 @import ... layer(vendor) 包一层,感受一下不需要抬高选择器就能覆盖第三方样式的轻松感。

