1261 字
6 分鐘

CSS @layer 怎麼排?沒分層的樣式為什麼總是贏

本文目錄

按鈕寫了 class="btn p-4",畫面上的 padding 卻是 0。打開 DevTools,.p-4 的規則被劃掉了,贏的是 CSS 檔裡一行很普通的 .btn { padding: 0; }。兩者都只有一個 class,specificity 相同,決定勝負的是 layer。

這是 Tailwind CSS v4 專案常見的狀況。v4 改用瀏覽器原生的 cascade layers,utility 全部放在 @layer utilities 裡(Tailwind CSS 升級指南);而自己寫、沒包進任何 layer 的 .btn,依 cascade 規則會贏過所有分層的樣式。

用 Tailwind CSS v4.3.5 編譯、在 Chrome 157 實測:

寫法按鈕的 padding-top
.btn { padding: 0; } 直接寫在 @import "tailwindcss"; 後面0px
改成 @layer components { .btn { padding: 0; } }16px

修法就是把自訂元件樣式放回 @layer components,讓 utility 層可以覆寫它。要理解為什麼,得先看 @layer 的排序規則。

@layer 的三條規則#

MDN 的 @layer 說明寫得很完整,實際會影響除錯的是這三條:

  1. 層的先後比 specificity 優先。 後宣告的層贏,即使它的選擇器比較簡單。
  2. 沒分層的樣式排在所有層之後。 不管寫在檔案的哪個位置,一般宣告都會贏過任何 layer 裡的宣告。
  3. !important 會把順序反過來。 越早宣告的層,!important 越強;分層的 !important 也會贏過沒分層的 !important。

下面這段可以直接存成 HTML,用瀏覽器打開,對照每個元素的計算結果:

<style>
@layer reset, components, utilities;
@layer components { .card .title { color: green; } }
@layer utilities { .text-red { color: red; } }
p { margin: 3px; }
@layer reset { p { margin: 0; } }
@layer components { .imp { color: green !important; } }
@layer utilities { .imp { color: red !important; } }
</style>
<div class="card"><h2 class="title text-red">紅色</h2></div>
<p>margin 是 3px</p>
<span class="imp">綠色</span>
  • 標題是紅色:.text-red 只有一個 class,仍然贏過兩個 class 的 .card .title,因為 utilities 層較晚宣告。
  • 段落的 margin 是 3px:沒分層的 p 寫在 @layer reset 前面,照樣勝出。
  • .imp 是綠色:兩邊都是 !important,較早的 components 反而贏。

第三條最容易讓人誤會。如果團隊習慣用 !important 臨時壓過樣式,導入 layer 之後要先清點這些地方,因為它的勝負方向和一般宣告相反。

既有專案怎麼排層#

第一行先用一個 @layer 陳述式把順序定下來。順序以第一次出現為準,之後在其他檔案追加同名的層,不會改變它的位置。

@layer reset, vendor, legacy, components, utilities;
@import url("vendor/datepicker.css") layer(vendor);
@import url("legacy.css") layer(legacy);

@import 必須放在其他規則前面,只有 @charset 和 @layer 陳述式可以排在它之前。

我會這樣分配:

  • reset:重置與全站基礎樣式,越早越好,任何人都能覆寫。
  • vendor:第三方套件的 CSS。放進 layer 之後,它的選擇器寫得再長,自家元件樣式都能用簡單的 class 蓋過去,不必再拚 specificity。
  • legacy:還沒整理的舊樣式,下面會說明為什麼需要這一層。
  • components:自家元件。
  • utilities:單一用途的輔助 class,排最後,讓它在 HTML 上永遠有最終決定權。

實測時,第三方的 .dropdown .item { padding: 12px; } 放進 vendor 層後,元素上的單一 class .p-1 就能把 padding 改成 4px。

導入時最需要注意的,是還沒搬進 layer 的舊樣式會一下子變成最強的那一層。邊搬邊上線的話,舊 CSS 會壓過剛搬進 components 的新寫法,看起來像是搬壞了。所以我會先把整份舊 CSS 整包放進上面的 legacy 層,再逐步把規則搬到正確的層。legacy 排在 vendor 後面,舊程式碼原本對第三方套件的覆寫仍然有效;排在 components 前面,新寫的元件樣式從第一天起就能蓋過它。

Tailwind CSS v4 自己宣告了 theme, base, components, utilities 四層。用 v4 時,自訂元件寫進 @layer components,想讓 hover:、md: 這些 variant 套用到自訂 class,則改用 @utility。v3 的 @layer 是由 Tailwind 在編譯時搬移規則,不是瀏覽器的 cascade layers,從 v3 升級時要特別留意這個差異。

值不值得導入#

**值得:**專案裡有第三方 CSS、正在用 Tailwind CSS v4,或已經出現為了蓋過別人而一路加長選擇器、加 !important 的情況。layer 能把「誰該贏」寫成一行明確的宣告,比靠檔案順序和 specificity 猜測好維護。

**可以先不做:**小型網站、樣式全部自己寫、也沒有覆寫衝突的話,導入 layer 主要是多一層概念要記,收益不大。

@layer 自 2022 年 3 月起在各主要瀏覽器可用,MDN 標為 Baseline 廣泛可用,相容性通常不是考量重點。真正的成本在團隊理解:「沒分層的樣式最強」和「!important 反轉」這兩條規則,要讓每個會改 CSS 的人都知道,否則下一次除錯時又會回到加長選擇器的老路。

本文範例在 2026-11-14 於 macOS 上的 Chrome 157(headless)執行,以 getComputedStyle() 讀取結果;Tailwind 的情境使用 @tailwindcss/cli 4.3.5 編譯後測試。

參考資料:

MDN:@layer

CSS Cascading and Inheritance Level 5:Cascade Layers

Tailwind CSS:Upgrade guide

Tailwind CSS v3:Functions & Directives

CSS @layer 怎麼排?沒分層的樣式為什麼總是贏
https://laplusda.com/posts/css-cascade-layers-unlayered-styles/
作者
Zero
發佈於
2026-11-14
許可協議
CC BY-NC-SA 4.0