你以为第三方 CSS 只能用 !important 覆盖?今天 @layer 把这件事彻底原生化了
你以为第三方 CSS 只能用 !important 覆盖?今天 @layer 把这件事彻底原生化了。
每次接一个 UI 框架进来,设计师都要跟你要源文件——因为框架里那些 .btn-primary 用 #sidebar .modal .content .btn-primary 写的,你用普通 class 根本压不住。最后就是两行 !important,然后整个团队都开始疯狂加 !important,三个月后没人知道为什么任何样式能生效。
这是特异性战争的终态。CSS 一直缺一个机制说「我这个层就是要比那个层优先级高」,直到 @layer 把这件事原生化了。
@layer 的核心逻辑:层顺序 > 选择器特异性
@layer 的工作方式很简单:你在文件顶部声明层的顺序,后面的层天然比前面的层优先级高,不管你的选择器有多弱。
/* 顶层声明顺序,后面 wins */
@layer reset, framework, components, utilities;
/* 权重最低 —— 即使写成 #app .btn 也输给 framework */
@layer reset {
* { box-sizing: border-box; margin: 0; padding: 0; }
}
/* 权重中等 —— framework 里的规则压不过它 */
@layer components {
.btn { padding: 0.75rem 1.5rem; border-radius: 0.375rem; }
}
/* 权重最高 —— 即使只写一个 .btn 也赢 */
@layer utilities {
.btn { border-radius: 1rem; } /* 赢 */
}
关键点:层顺序决定优先级,选择器特异性只在同一个层内起作用。这从根本上消灭了特异性战争。
@import layer():把第三方框架装进低优先级层
真正让 @layer 成为「第三方 CSS 救星」的功能是 @import url(...) layer(name)。你只需要两行代码:
/* 第一步:声明层顺序,把 framework 放在最低 */
@layer reset, framework, components, utilities;
/* 第二步:把第三方框架装进 framework 层 */
@import url("tailwindcss/dist/styles.min.css") layer(framework);
@import url("bootstrap/dist/css/bootstrap.min.css") layer(framework);
现在,无论 Bootstrap 里写得多变态——#app .container .row .col .card .btn ——你只需要在 components 或 utilities 层写一个 .btn,天然赢,无需 !important。
对比一下没有 @layer 的时候:
/* 以前:没有 @layer,只能靠特异性硬怼 */
#sidebar .modal .content .card .btn-primary {
background-color: blue !important; /* 脆弱,一换框架就废 */
}
/* 现在:@layer 之后,清清爽爽 */
@layer framework, components;
@import url("bootstrap.css") layer(framework);
@layer components {
.btn-primary {
background-color: blue; /* 赢,不需要 !important */
}
}
三种 @import layer() 的用法
用法一:把 npm 包里的 CSS 直接 import 进层
如果你的框架 CSS 是经过构建工具导入的(比如 PostCSS 里的 @import),写法一样:
@layer framework, components;
@layer framework {
@import "bootstrap/dist/css/bootstrap.css";
@import "@radix-ui/themes/styles.css";
}
用法二:匿名层 —— 一次性隔离不想管的 CSS
不想给第三方 CSS 起名字?用匿名层:
@import url("some-utility.css") layer();
匿名层不能被引用,但同样享受层顺序的优先级保护。
用法三:嵌套层 —— 框架内部再分层
@layer framework, components;
@layer framework {
@import "bootstrap.css" layer(framework.bootstrap);
@import "fontawesome.css" layer(framework.icons);
}
嵌套层用点号语法 @layer framework.bootstrap,优先级只在父层内部起作用,不影响其他层。
Tailwind CSS v4 自带 @layer——你已经在用了
Tailwind CSS v4 内部已经全面使用 @layer,它的 base、components、utilities 三层是语言级的,不需要编译时指令:
@import "tailwindcss";
/* 你的自定义组件插在 Tailwind 正确的层里 */
@layer components {
.card {
@apply bg-white rounded-2xl shadow-lg border border-gray-100;
}
}
如果你用的是 Tailwind v3 或者其他框架,@layer 同样适用,原理完全一样。
三个必须知道的坑
坑一:<link> 标签引入的 CSS 是无层的
<!-- 这种方式引入的 CSS 不在任何层,会压过所有 @layer -->
<link rel="stylesheet" href="bootstrap.css">
<!-- 正确做法:用 @import 在 @layer 里引入 -->
如果框架 CSS 已经通过 <link> 加载,你没法把它移进层——这种情况下只能把自定义 CSS 放到 <link> 之后作为「无层」样式来赢它。
坑二:!important 在层内是反过来的优先级
正常层规则:后面的层赢。
!important 规则:前面的层赢。
@layer base, components;
@layer base {
[hidden] { display: none !important; } /* 赢 —— 前面层 + !important */
}
@layer components {
.modal { display: block !important; } /* 输 —— 后面层 + !important */
}
这是设计好的机制,让 reset 层能保护关键样式。但如果你不知道,就会踩坑。
坑三:未层化的样式天然赢过所有层
@layer components {
.btn { background: blue; } /* 输 */
}
.btn { background: red; } /* 赢 —— 不在任何层 */
一旦开始用 @layer,建议把所有 CSS 都放进去,包括你的自定义样式。否则「意外的无层样式」会悄悄盖过你的层规则。
三步落地
第一步:审计你的第三方 CSS 引入方式
检查 index.html 里有没有 <link> 标签引入框架 CSS,有的话改成 CSS 文件里的 @import layer()。
/* 改之前 */
<link rel="stylesheet" href="bootstrap.min.css">
/* 改之后:在 CSS 里 */
@import url("bootstrap.min.css") layer(framework);
第二步:声明层顺序
在主样式表最顶部写一行,确立优先级:
@layer reset, framework, base, components, utilities, overrides;
第三步:把你的覆盖代码放进高优先级层
@layer components {
/* 任何框架的按钮样式都会被这个压过,不需要 !important */
.btn-primary {
background-color: var(--brand);
border-color: var(--brand);
}
}
现在接 UI 框架,不需要源文件,不需要 !important,也不需要和设计师解释「为什么我的 class 压不过它的 ID 选择器」。三层声明,框架进低层,你的东西进高层,浏览器自己会算。
评论区
登录后可评论。