CSS @layer 我用了三年,今天才发现它真正值钱的地方不是「谁优先级高」——是把整个 cascade 变成可管理的目录结构

写 CSS 最怕什么?接手别人的代码,发现 .button 写了三遍,每一遍都叠了一堆 !important,根本不知道哪个生效。@layer 出来好几年了,大多数人只会写 @layer reset {},但它真正的能力是把整个 cascade 变成可管理的目录结构——今天把这件事说清楚。

一、为什么你还在用 !important 忍着

CSS 的 cascade 优先级规则写了快三十年,大多数团队靠「后来居上」和 !important 维持秩序。问题在于这不是规则,是潜规则——换一个接手的人又要重新摸索一遍。

@layer 把 cascade 变成了显式的目录管理。每个 layer 有一个固定的优先级顺序:写在后面的 layer 赢。不需要算 specificity,不需要 !important,直接看 layer 顺序就知道谁覆盖谁。

@layer base {
  .btn { color: blue; }
}
@layer components {
  .btn { color: red; } /* components 写在 base 后面,所以 red 会赢 */
}

这就是 @layer 的核心逻辑:layer 之间的优先级由声明顺序决定,而不是 specificity。

二、layer 的声明顺序才是关键

很多教程把 @layer 讲复杂了,其实你只需要记住一条:同一 layer 内,后来居上;不同 layer 间,声明顺序决定一切

/* 声明顺序决定优先级 */
@layer reset, base, components, utilities;
/* reset 优先级最低,utilities 优先级最高 */

声明顺序可以抽离到最前面,后续直接在各自 layer 里写样式,互不干扰:

/* 统一声明顺序 */
@layer reset, base, theme, components, utilities;

@layer reset {
  * { margin: 0; padding: 0; box-sizing: border-box; }
}

@layer base {
  body { font-family: system-ui; }
}

@layer theme {
  :root { --primary: #3b82f6; }
  .btn { background: var(--primary); }
}

@layer components {
  .btn { border-radius: 6px; padding: 8px 16px; }
  .btn-primary { background: #1d4ed8; } /* 优先级高于 theme 的 .btn */
}

@layer utilities {
  .hidden { display: none; }
  .text-center { text-align: center; }
}

这样不管你在 components 里怎么写 .btn,只要 utilities 在最后,它就比 utilities 里的同名类优先级低。这是显式规则,不是靠 specificity 硬算出来的。

三、设计系统的正确打开方式

前端团队做设计系统,最怕设计师改了个颜色,你的代码要翻三个文件手动同步。@layer 配合 CSS 自定义属性,可以把设计系统的边界彻底管住。

@layer design-system {
  :root {
    --ds-color-primary: #3b82f6;
    --ds-color-text: #1f2937;
    --ds-radius: 6px;
  }

  .ds-button {
    background: var(--ds-color-primary);
    color: white;
    border-radius: var(--ds-radius);
    padding: 8px 16px;
    border: none;
    cursor: pointer;
  }
}

@layer overrides {
  /* 业务代码可以安全地覆盖 design-system,不会污染全局 */
  .marketing-cta .ds-button {
    background: linear-gradient(135deg, #3b82f6, #8b5cf6);
    font-weight: 600;
  }
}

业务层的 override 不会意外影响其他地方的按钮样式,因为它们在不同的 layer 层级——design-system 的 layer 优先级低于 overrides。

四、layer 嵌套:复杂组件的正确姿势

有些人写了 @layer 之后还是遇到 specificity 冲突,问题在于 layer 可以嵌套,但外层 layer 的优先级规则不变。

@layer components {
  .card {
    border: 1px solid #e5e7eb;
    border-radius: 8px;
    padding: 16px;
  }

  @layer card-header {
    .card-header {
      font-weight: 600;
      border-bottom: 1px solid #e5e7eb;
      padding-bottom: 12px;
    }
  }

  @layer card-footer {
    .card-footer {
      border-top: 1px solid #e5e7eb;
      padding-top: 12px;
      margin-top: 16px;
    }
  }
}

嵌套 layer 的优先级在外层之内,不影响外层和其他 layer 之间的优先级关系。

五、这些坑我帮你踩过了

坑 1:未声明的 layer 优先级最高

没有用 @layer xxx {} 包裹的样式,会被放进一个隐式的「无名称 layer」,它的优先级比所有显式声明的 layer 都高。

@layer reset, base;

.base { color: blue; } /* 隐式 layer,优先级最高,blue 会赢 */

所以确保所有样式都放进某个 layer,或者明确知道隐式 layer 的存在。

坑 2:layer 顺序只能声明一次

重复声明同一个 layer 名称会把两个块合并,不是替换。

@layer components {
  .btn { color: blue; }
}
@layer components { /* 这个块会和上面的 .btn { color: blue; } 合并 */
  .btn { padding: 8px; } /* 两条规则都在 components layer 内 */
}

这不是 bug,是设计——同一个 layer 里的规则天然会 cascade 在一起。

坑 3:import 的 layer 优先级取决于 import 位置

@layer reset;
@import url('normalize.css') layer(reset); /* normalize 会被放进 reset layer */
@layer base;

如果 import 在 base layer 声明之后放进 reset,它仍然属于 reset layer,优先级不受 import 位置影响。

六、下一步:把你们的样式表重构一下

把手头的 CSS 项目按这个结构整理一遍:

  1. 抽离出 @layer 声明顺序,放到文件最顶部
  2. 把现有的 CSS 分类进各个 layer:reset → base → theme → components → utilities
  3. 删掉所有 !important,改成调整 layer 顺序或把样式放进更高级的 layer

改动不大,但效果立竿见影——以后谁接手代码,看一眼 layer 声明顺序就知道优先级是什么,不用再靠猜了。

评论区

0 条评论

登录后可评论。