先记住这个答案
@layer 的作用是把样式规则分组,让浏览器在比较优先级时先看「这条规则属于哪个层」,再看选择器权重。层的优先级完全由声明顺序决定:越靠后声明的层优先级越高。没有被放进任何层的样式,优先级比所有层都高。
- 越靠后声明的层优先级越高
- 层间比较不看选择器权重
- 未分层样式优先级高于所有层
@layer a, b可预声明顺序
一个场景理解层的作用
想象你的项目引入了一套第三方 UI 库,库里用了很高权重的选择器。你想覆盖它,以前只能写更长的选择器甚至加 !important。有了 @layer,可以把库的样式放到低优先级层里,你自己的样式放到高优先级层里,不管选择器怎么写,你的层永远赢。
/* 先声明顺序:base 在前,components 在后 */
@layer base, components;
@layer base {
.btn { color: gray; } /* 低优先级层 */
}
@layer components {
.btn { color: blue; } /* 高优先级层,最终生效 */
}结果是蓝色。即使把 base 层里的选择器改成 #app .wrapper .btn,它也赢不了 components 层里的 .btn,因为层的顺序比选择器权重优先。
三种声明方式
@layer 有三种用法,项目里最常见的是预声明顺序再分别填充规则。
/* 1. 预声明顺序,不写任何规则 */
@layer reset, base, components, utilities;
/* 2. 分别往各层里放规则,位置随意 */
@layer components { .card { padding: 16px; } }
@layer reset { * { margin: 0; } }
@layer utilities { .mt-4 { margin-top: 1rem; } }虽然 reset 层的代码写在 components 后面,但预声明里 reset 排第一,优先级最低。utilities 排最后,优先级最高。这就是预声明的好处:让层的顺序和代码的书写位置脱钩。
第二种是直接写 @layer base { ... },不预声明。这时顺序按第一次出现来算。第三种是匿名层 @layer { ... },它没有名字,永远排在最末尾,但匿名层不能被追加规则,实际项目里不常用。
未分层样式为什么最强
没有写在任何 @layer 里面的样式,在层叠排序中排在所有命名层之后,也就是优先级最高。这个设计是为了向后兼容:老项目没用 @layer,新加的层不会意外覆盖已有样式。
这也意味着,如果你把第三方库用 @layer 包起来,你自己未分层的样式天然就能覆盖它,不需要任何额外的权重技巧。比如 Tailwind CSS v4 默认就把自己的样式放在层里,用户写的自定义样式在层外面,所以总是能覆盖 Tailwind 的类。
容易答错的地方
- 以为选择器权重能跨层比较
- 同一层内选择器权重照常生效,但跨层比较时权重完全不看。低优先级层里写个
#id .class tag也赢不了高优先级层里的一个类选择器。很多人第一次用@layer会困惑「明明我选择器更具体,为什么没生效」,原因就是层间优先级比选择器权重更靠前。 - 以为代码位置决定层的顺序
- 没有预声明时确实按第一次出现决定,但有预声明时,
@layer a, b, c;这一行就锁定了顺序。后面不管代码写在哪里,a 永远最低,c 永远最高。把 c 层的代码写在文件顶部不会让它优先级变低。 - 漏掉未分层样式的优先级
- 用了
@layer之后如果还有样式没包进任何层,这些样式的优先级比所有层都高。调试时发现某条规则怎么都覆盖不了,先检查目标样式是不是写在层外面。
面试官还会怎么问?
@layer 和 !important 一起用会怎样?
!important 会把层的优先级顺序反转。普通样式里 utilities 层赢 base 层,但两边都加 !important 时 base 层反而赢,因为 !important 的层叠排序是反着来的。所以第三方库用 @layer 包起来后,如果库里写了 !important,它反而变得更难被覆盖。
怎么把第三方 CSS 文件整个放进一个层?
用 @import 配合 layer 关键字:@import url('lib.css') layer(vendor);。这样整个文件的规则都属于 vendor 层,不用手动修改第三方代码。
嵌套层的优先级怎么算?
层可以嵌套,写成 @layer outer { @layer inner { ... } } 或用点号 @layer outer.inner { ... }。嵌套层的优先级在父层内部排序,外层的优先级不变。比如 base 层里有两个子层 base.reset 和 base.typography,它们之间的顺序只影响 base 内部,不会跑到 components 层前面去。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。