先记住这个答案
没有放进任何 @layer 的样式,优先级比所有命名层都高。不管层里的选择器写得多具体,未分层的一个类选择器就能赢。浏览器在做层叠排序时,把所有未分层样式归入一个隐含的最高优先级层。
- 未分层样式优先级高于所有层
- 跨层比较不看选择器权重
- 加
!important后未分层反而最弱 - 第三方库放进层里就能轻松覆盖
一段代码看清谁赢谁输
下面的代码里,层内用了 ID 选择器,层外只用了类选择器,但层外赢了。
@layer components {
#app .btn { color: red; } /* 层内,权重 (1, 1, 0) */
}
/* 未分层 */
.btn { color: blue; } /* 层外,权重 (0, 1, 0),但赢了 */.btn 显示蓝色。层外的 .btn 优先级排在 components 层之后,层间比较不看权重,后面的层直接赢。
这就是把第三方库放进 @layer 里的好处:库里不管用了多高权重的选择器,你在层外面随便写一个类就能覆盖它,再也不用费劲凑更长的选择器或者加 !important。
为什么要这样设计
@layer 是后来加入 CSS 的特性。如果未分层样式排在层的下面,现有的项目一旦引入 @layer,所有老样式的优先级都会变低,页面立刻乱掉。让未分层样式保持最高优先级,就能让团队渐进迁移:先把第三方库和 reset 放进层里,自己的业务代码暂时不动,等稳定了再逐步归层。
Tailwind CSS v4 就是利用了这个特性。Tailwind 把自己的工具类放进 @layer utilities 里,用户写的自定义样式在层外面,天然就能覆盖 Tailwind 的类,不需要任何配置。
!important 让未分层变最弱
普通声明里未分层最强,但加了 !important 之后完全反转:未分层的 !important 变成最弱。
!important 赢未分层 !importantCSS@layer base {
.tip { color: red !important; } /* 层内 !important,赢了 */
}
.tip { color: blue !important; } /* 未分层 !important,输了 */.tip 显示红色。重要声明的层叠顺序和普通声明完全反转,先声明的层反而强,未分层从最强变成最弱。这个规则很容易踩坑:你在层外加了 !important 想覆盖层里的 !important,结果发现不管用,因为反转后你的优先级反而更低。
遇到这种情况,正确做法是把你的样式也放进一个层里,而且放到比对方更早声明的层里。因为重要声明中先声明的层更强。
容易答错的地方
- 答成「看选择器权重」
- 跨层比较时选择器权重完全不参与。面试时很多人会下意识回答「ID 选择器优先级更高」,在有
@layer的场景里这是错的。先确认两条规则是否在同一层,不在同一层就只比层的顺序。 - 忘记
!important反转的情况 - 未分层样式在普通声明中最强,很多人默认
!important也一样。但!important会反转整个层序,未分层!important变成最弱。这是面试中非常常见的追问点。
面试官还会怎么问?
怎么把一个第三方 CSS 文件整体放进层里?
用 @import url('lib.css') layer(vendor);,整个文件的规则都属于 vendor 层。如果你的样式不在任何层里,天然就能覆盖它。
匿名层和未分层样式一样吗?
不一样。@layer { ... } 创建一个没有名字的层,它属于命名层体系的一部分,优先级按出现顺序排。未分层样式不属于任何层,优先级最高。匿名层的优先级不一定比命名层高,但未分层样式一定比所有层高。
项目全部迁移到 @layer 后,还有未分层样式吗?
理想情况下没有了,所有样式都在层里面。但实际项目中浏览器默认样式(User Agent stylesheet)是未分层的,而且行内 style 属性也不在任何层里。浏览器默认样式在层叠排序中有自己的来源级别,行内样式排在所有层之上。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。