一个项目跑了两年之后,样式表通常会长成三种东西的混合物:组件样式、工具类,以及一堆用来"压过别人"的补丁。
.page .card .card-body .btn.primary {
background: #7c5cff !important;
}
写这行代码的人并不想这样,他只是没有别的办法。
# @layer 把优先级变成声明
@layer reset, base, components, utilities;
这一行的意思是:后声明的层永远赢,与选择器特异性无关。
于是 utilities 里的一个简单类:
@layer utilities {
.mt-0 { margin-top: 0; }
}
可以稳定地压过 components 里那个写了五个类的复杂选择器:
@layer components {
.page .card .card-body .title { margin-top: 24px; }
}
不需要 !important,也不需要提高特异性。层级关系是显式的,是可 review 的。
# 推荐的层结构
@layer reset,
tokens,
base,
layout,
components,
utilities,
overrides;
从低到高,语义清晰:
- reset —— 抹平浏览器差异
- tokens —— 设计变量,几乎不产生规则
- base —— 元素默认样式
- layout —— 栅格、容器
- components —— 组件自己的一亩三分地
- utilities —— 单职责工具类,永远能赢
- overrides —— 临时补丁层,方便日后清理
# 和第三方库和平共处
这是 @layer 最实用的场景:把 UI 库整个塞进低优先级层。
@import url('vendor-ui.css') layer(vendor);
@layer vendor, components, utilities;
库里的 .button { padding: 8px 16px } 依然生效,但你的 .btn-lg 可以轻松覆盖它,不用再写 !important 对抗。
一个容易踩的坑:
@import必须写在所有规则之前。如果打包器把 CSS 拼接顺序打乱,层顺序可能不是你预期的——显式写一行@layer a, b, c;声明顺序最保险。
# 渐进增强的写法
老浏览器不认识 @layer,会直接忽略整条 at-rule 里的内容(而不是报错崩溃)。所以可以这样兜底:
/* 老浏览器走这条 */
.btn { padding: 10px 20px; }
/* 新浏览器用层来管理 */
@layer components {
.btn { padding: 10px 20px; }
}
代价是重复一次声明,换来的是完全不依赖构建工具的兼容性。
# 迁移顺序建议
不要一次性重写。按这个顺序动,风险最小:
- 先加一行
@layer声明,什么都不放 - 把 reset 和 tokens 搬进去
- 把工具类搬进 utilities,删掉对应的
!important - 最后处理组件层,一边搬一边删掉为了压优先级而加的选择器
层叠不是敌人。它只是过去缺少一个可以表达"谁该赢"的语法。现在有了。