CSS @layer 我用了三年,今天才发现它真正值钱的地方不是「谁优先级高」——是把整个 cascade 变成可管理的目录结构
写 CSS 最怕什么?接手别人的代码,发现 .button 写了三遍,每一遍都叠了一堆 !important,根本不知道哪个生效。@layer 出来好几年了,大多数人只会写 @layer reset {},但它真正的能力是把整个 cascade 变成可管理的目录结构——今天把这件事说清楚。
一、为什么你还在用 !important 忍着
CSS 的 cascade 优先级规则写了快三十年,大多数团队靠「后来居上」和 !important 维持秩序。问题在于这不是规则,是潜规则——换一个接手的人又要重新摸索一遍。
@layer 把 cascade 变成了显式的目录管理。每个 layer 有一个固定的优先级顺序:写在后面的 layer 赢。不需要算 specificity,不需要 !important,直接看 layer 顺序就知道谁覆盖谁。
@layer base {
.btn { color: blue; }
}
@layer components {
.btn { color: red; } /* components 写在 base 后面,所以 red 会赢 */
}
这就是 @layer 的核心逻辑:layer 之间的优先级由声明顺序决定,而不是 specificity。
二、layer 的声明顺序才是关键
很多教程把 @layer 讲复杂了,其实你只需要记住一条:同一 layer 内,后来居上;不同 layer 间,声明顺序决定一切。
/* 声明顺序决定优先级 */
@layer reset, base, components, utilities;
/* reset 优先级最低,utilities 优先级最高 */
声明顺序可以抽离到最前面,后续直接在各自 layer 里写样式,互不干扰:
/* 统一声明顺序 */
@layer reset, base, theme, components, utilities;
@layer reset {
* { margin: 0; padding: 0; box-sizing: border-box; }
}
@layer base {
body { font-family: system-ui; }
}
@layer theme {
:root { --primary: #3b82f6; }
.btn { background: var(--primary); }
}
@layer components {
.btn { border-radius: 6px; padding: 8px 16px; }
.btn-primary { background: #1d4ed8; } /* 优先级高于 theme 的 .btn */
}
@layer utilities {
.hidden { display: none; }
.text-center { text-align: center; }
}
这样不管你在 components 里怎么写 .btn,只要 utilities 在最后,它就比 utilities 里的同名类优先级低。这是显式规则,不是靠 specificity 硬算出来的。
三、设计系统的正确打开方式
前端团队做设计系统,最怕设计师改了个颜色,你的代码要翻三个文件手动同步。@layer 配合 CSS 自定义属性,可以把设计系统的边界彻底管住。
@layer design-system {
:root {
--ds-color-primary: #3b82f6;
--ds-color-text: #1f2937;
--ds-radius: 6px;
}
.ds-button {
background: var(--ds-color-primary);
color: white;
border-radius: var(--ds-radius);
padding: 8px 16px;
border: none;
cursor: pointer;
}
}
@layer overrides {
/* 业务代码可以安全地覆盖 design-system,不会污染全局 */
.marketing-cta .ds-button {
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
font-weight: 600;
}
}
业务层的 override 不会意外影响其他地方的按钮样式,因为它们在不同的 layer 层级——design-system 的 layer 优先级低于 overrides。
四、layer 嵌套:复杂组件的正确姿势
有些人写了 @layer 之后还是遇到 specificity 冲突,问题在于 layer 可以嵌套,但外层 layer 的优先级规则不变。
@layer components {
.card {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 16px;
}
@layer card-header {
.card-header {
font-weight: 600;
border-bottom: 1px solid #e5e7eb;
padding-bottom: 12px;
}
}
@layer card-footer {
.card-footer {
border-top: 1px solid #e5e7eb;
padding-top: 12px;
margin-top: 16px;
}
}
}
嵌套 layer 的优先级在外层之内,不影响外层和其他 layer 之间的优先级关系。
五、这些坑我帮你踩过了
坑 1:未声明的 layer 优先级最高
没有用 @layer xxx {} 包裹的样式,会被放进一个隐式的「无名称 layer」,它的优先级比所有显式声明的 layer 都高。
@layer reset, base;
.base { color: blue; } /* 隐式 layer,优先级最高,blue 会赢 */
所以确保所有样式都放进某个 layer,或者明确知道隐式 layer 的存在。
坑 2:layer 顺序只能声明一次
重复声明同一个 layer 名称会把两个块合并,不是替换。
@layer components {
.btn { color: blue; }
}
@layer components { /* 这个块会和上面的 .btn { color: blue; } 合并 */
.btn { padding: 8px; } /* 两条规则都在 components layer 内 */
}
这不是 bug,是设计——同一个 layer 里的规则天然会 cascade 在一起。
坑 3:import 的 layer 优先级取决于 import 位置
@layer reset;
@import url('normalize.css') layer(reset); /* normalize 会被放进 reset layer */
@layer base;
如果 import 在 base layer 声明之后放进 reset,它仍然属于 reset layer,优先级不受 import 位置影响。
六、下一步:把你们的样式表重构一下
把手头的 CSS 项目按这个结构整理一遍:
- 抽离出
@layer声明顺序,放到文件最顶部 - 把现有的 CSS 分类进各个 layer:reset → base → theme → components → utilities
- 删掉所有
!important,改成调整 layer 顺序或把样式放进更高级的 layer
改动不大,但效果立竿见影——以后谁接手代码,看一眼 layer 声明顺序就知道优先级是什么,不用再靠猜了。
评论区
登录后可评论。