你以为第三方 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 ——你只需要在 componentsutilities 层写一个 .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,它的 basecomponentsutilities 三层是语言级的,不需要编译时指令:

@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 选择器」。三层声明,框架进低层,你的东西进高层,浏览器自己会算。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 13 阅读