📋 目錄
CSS 的 Specificity(優先級)問題,大概是每一個 CSS 寫久了的人都會踩過的坑。你加了一條
.button.primary的樣式,結果被.form .button覆蓋了;你再加一條!important,結果這個!important開始在專案裡擴散,最後到處都是!important,沒有人記得為什麼一開始要用。@layer的出現,終於提供了一個不用!important就能控制樣式優先順序的方法。本文整理 @layer 的觀念和實際應用場景。
前端工程師為什麼要關心 @layer
CSS 的 cascade(層疊)規則,本質上是用「後來居上」(last wins)原則解決樣式衝突。但當你的專案變大、CSS 檔案變多、「後來」的意思就越來越模糊。
大型前端專案裡的 CSS 問題通常長這樣:
/* 你寫的 */
.my-component .title {
color: blue;
}
/* 第三方 UI Library 加的 */
[data-theme="dark"] .component-wrapper .title {
color: white;
}
/* 某個舊同事加的 */
.title {
color: red !important; /* 為什麼這裡有 !important?沒人知道 */
}
/* 結果:這個 .title 到底是什麼顏色? */
@layer 解決的問題:讓你有辦法明確宣告「哪個來源的樣式,優先於哪個」,而不是靠選擇器的複雜度或 !important 來猜測結果。
@layer 的基本語法
定義一個 Layer
/* 定義一個名為 "reset" 的 layer */
@layer reset {
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
}
/* 定義一個名為 "base" 的 layer */
@layer base {
body {
font-family: system-ui, sans-serif;
line-height: 1.5;
}
h1 { font-size: 2rem; }
h2 { font-size: 1.5rem; }
}
/* 定義一個名為 "components" 的 layer */
@layer components {
.button {
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
}
.card {
border: 1px solid #e5e5e5;
border-radius: 8px;
}
}
/* 定義一個名為 "utilities" 的 layer */
@layer utilities {
.text-center { text-align: center; }
.mt-1 { margin-top: 1rem; }
.hidden { display: none; }
}
Layer 的優先順序規則
後定義的 layer,優先於先定義的 layer。
@layer reset;
@layer base;
@layer components;
@layer utilities;
/* utilities 優先於 components
components 優先於 base
base 優先於 reset */
也就是說,這三條規則同時出現時:
@layer components {
.title { color: blue; }
}
@layer utilities {
.title { color: green; } /* 這個會勝出 */
}
@layer base {
.title { color: red; } /* 這個被 components 覆蓋 */
}
color: green 會勝出。因為 utilities 在 @layer 宣告順序中排在 components 和 base 之後。
Layer 與 Specificity 完全無關
@layer 的最重要特性:同一個 layer 內的優先順序,還是依照 standard CSS specificity 規則。 但跨 layer 的時候,layer 的順序才是決定因素,specificity 不論多高都沒用。
/* 不管這個選擇器多強,只要它在同一個 layer 內,就按 specificity 決定 */
@layer components {
/* specificity: 0,1,0 */
.card .title {
color: blue; /* 勝出 */
}
/* specificity: 0,2,0 */
.card .title.special {
color: purple; /* 因為 specificity 更高,所以這個勝出 */
}
}
/* utilities 的任何規則,即使 specificity 只有 0,0,1,
也會覆蓋 components 裡所有針對 .title 的規則 */
@layer utilities {
.title {
color: green; /* 會覆蓋上面兩條,即使 specificity 更低 */
}
}
這個行為看起來可能有點反直覺,但這正是 @layer 的設計目的:讓你先決定「架構」,再讓 specificity 只在「同一個架構層級內」起作用。
!important 的替代方案
!important 會讓一條規則無視 specificity 和 cascade 順序,強行覆蓋一切。這種做法簡單粗暴,但代價是讓你的 CSS 系統失去可預測性。
@layer 讓 !important 變得不必要
/* ❌ 過去的做法:用 !important */
/* third-party.css */
.button {
color: red !important; /* 強行覆蓋,阻礙任何自定義 */
}
/* custom.css */
.button {
color: blue; /* 無法覆蓋 !important */
}
/* ✅ 現在的做法:用 @layer */
/* 先宣告 layer 順序 */
@layer third-party, custom;
/* 讓 third-party.css 的規則在 custom layer 之前 */
@layer third-party {
@import url('third-party.css') layer(third-party);
}
@layer custom {
.button {
color: blue; /* 這個會勝出,因為 custom 排在 third-party 之後 */
}
}
custom layer 的樣式優先於 third-party layer,不需要 !important,結果完全可預測。
匿名 Layer
如果你只需要一個一次性的 layer,不想給它命名,可以用匿名 layer:
@layer reset {
* { margin: 0; }
}
@layer {
/* 匿名 layer,沒有名字 */
/* 這個 layer 的優先順序在 reset 之後 */
body {
background: #f5f5f5;
}
}
匿名 layer 的用處:隔離一段樣式,但不打算從外部引用它。
Layer 順序宣告的實務模式
方法一:先宣告順序,再寫樣式(推薦)
/* 第一步:宣告所有 layer 的順序(沒有內容)*/
@layer reset, base, framework, components, utilities;
/* 第二步:在各 layer 裡寫內容 */
@layer reset {
* { box-sizing: border-box; }
}
@layer base {
body { font-family: system-ui; }
}
@layer framework {
/* Bootstrap / Tailwind / Vuetify 的 CSS */
}
@layer components {
.button { padding: 0.5rem 1rem; }
.card { border-radius: 8px; }
}
@layer utilities {
.text-center { text-align: center; }
.hidden { display: none; }
}
先宣告順序的優點:一目了然。任何人都能在第一行看到整個專案的 CSS 層級架構,不需要翻到檔案各處去找 @layer 定義。
方法二:每個 layer 分別定義
/* reset.css */
@layer reset {
* { box-sizing: border-box; }
}
/* base.css */
@layer base {
body { font-family: system-ui; }
}
/* 最後在某個 entry file 宣告順序 */
@layer reset, base, components, utilities;
這種模式適合大型 Codebase,每個 layer 是獨立的 CSS 檔案,最後統一管理順序。
Design System 中的 @layer 應用
典型 Design System 的 CSS 層級
一個良好的 Design System,通常有以下幾層(由底到高):
/* 順序:foundation < tokens < base < components < patterns < utilities */
@layer foundation, tokens, base, components, patterns, utilities;
@layer foundation {
/* CSS Reset */
* { margin: 0; padding: 0; }
}
@layer tokens {
/* Design tokens(設計變數)*/
:root {
--color-primary: #3b82f6;
--color-secondary: #64748b;
--space-1: 0.25rem;
--space-2: 0.5rem;
}
}
@layer base {
/* HTML 元素預設樣式 */
body { color: var(--color-text); }
a { color: var(--color-primary); }
}
@layer components {
/* UI 元件 */
.btn { padding: var(--space-2) var(--space-4); }
.input { border: 1px solid var(--color-secondary); }
}
@layer patterns {
/* 多個元件組成的 Pattern */
.search-bar { display: flex; }
.card-grid { display: grid; gap: var(--space-4); }
}
@layer utilities {
/* 工具類 */
.hidden { display: none; }
.text-center { text-align: center; }
}
這個結構的價值:不管你的 component CSS 裡怎麼折騰,utilities layer 的 .hidden 永遠能覆蓋任何東西。
第三方 Framework 整合
如果你的 Design System 要整合 Tailwind CSS 或 Bootstrap:
/* 在你自己的 layer 之前引入 framework */
@layer framework {
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
}
/* 你的自定義樣式,永遠優先於 framework */
@layer components {
.btn-primary {
background: var(--color-primary);
/* 這個會勝過 Tailwind 的 .btn */
}
}
在 JavaScript 中控制 Layer 順序
// 動態改變 layer 順序
const stylesheet = document.styleSheets[0];
// layer 順序可以通過 insertRule() 來調整
// 這在需要根據使用者設定動態切換主題時很有用
// 實際應用:Dark Mode 切換
function switchTheme(theme) {
const style = document.getElementById('theme-layer');
// 動態調整 theme layer 的位置
if (theme === 'dark') {
// 把 theme layer 移到最高優先級
document.styleSheets[0].insertRule(
'@layer theme, reset, base, components, utilities;',
0
);
}
}
瀏覽器支援
@layer 在所有主流瀏覽器都已支援(Chrome 99+、Safari 15.4+、Firefox 97+),沒有任何理由不使用它。
/* Progressive Enhancement:如果瀏覽器不支援 @layer,就按正常 cascade 處理 */
@layer utilities {
.hidden {
display: none;
}
}
結語:什麼時候用 @layer
適合用 @layer 的場景:
- 大型前端專案,多人協作 CSS
- 需要整合多個第三方 CSS framework
- Design System 的 CSS 架構
- 任何你在考慮用
!important的時候
不需要 @layer 的場景:
- 極小的專案或一次性頁面
- CSS 架構已經有其他方案(如 CSS Modules、Styled Components)
@layer 不是要取代好的 CSS 架構,但它是在原生 CSS 範圍內管理 specificity 和 cascade 的最佳工具。當你發現你的 CSS 檔案裡開始出現 !important,這就是考慮用 @layer 重構的訊號。
延伸閱讀
- Interop 2026 CSS 新特性全景 — 2026 年 CSS 新功能瀏覽器支援狀態
- CSS :has() 父選擇器 — 另一個 CSS 選擇器新功能
本文是「2026 CSS 實用技術」系列文章之一。