TailwindCSS 配置系統:@theme CSS-first 設定完整解析與 tailwind.config.js 遷移 | TailwindCSS 完整教學

2026/08/04
TailwindCSS 配置系統:@theme CSS-first 設定完整解析與 tailwind.config.js 遷移 | TailwindCSS 完整教學

TailwindCSS v4 裡,「配置」不再是一個獨立的 JavaScript 檔案,而是直接寫進你的 CSS。核心是 @theme 指令——你在裡面定義的每一個設計 Token,會同時變成一個 CSS 變數,並自動生成對應的 utility class。這是 TailwindCSS 完整教學 系列的第四篇,帶你看懂 @theme 如何運作、extend 擴充與覆寫的分界、常見設定項的命名空間規則,以及如何從 tailwind.config.js 的思維遷移過來。

前言

**配置系統(configuration)**指的是你告訴 TailwindCSS「顏色、間距、字體、斷點這些設計值長什麼樣子」的機制。v3 把這件事集中在專案根目錄的 tailwind.config.js,用一個 JavaScript 物件描述整套設計系統;v4 則把它搬進 CSS,用 @theme 指令直接定義設計 Token——這就是所謂的 CSS-first 配置

用一個現實世界的類比來理解:把設計系統想成一台音響的控制面板。v3 的做法是把面板收在一個獨立的儀器箱(tailwind.config.js)裡,要調音色得先打開箱子、找到對應的旋鈕、翻譯成 JS 物件的巢狀結構。v4 則把所有旋鈕、滑桿、色票直接鑲在 CSS 這塊面板上:你轉一個旋鈕(定義一個 --color-* 變數),面板上對應的燈號(bg-*text-* 等 utility)就同步亮起,而且這顆旋鈕的當前值也隨時能被面板上其他元件(任意 CSS 規則的 var())讀到。「設定」與「可用的變數」合而為一,正是 v4 最核心的心智轉變。

本篇你將學到:

  • @theme 的運作原理——為什麼一個設計 Token 能同時是 CSS 變數又能生成 utility,以及它與 :root 的關鍵差異
  • 命名空間規則--color-*--spacing-*--font-*--breakpoint-* 各自生成哪一類 utility
  • 擴充 vs 覆寫@theme extend(追加)與 @theme(覆蓋)的分界,以及何時該用哪一個
  • 如何用 @config 相容舊的 tailwind.config.js,以及一套從 JS 配置遷移到 @theme 的思維轉換

本系列以 TailwindCSS v4 為預設版本,遇到與 v3 有明顯差異之處會特別標註。

核心概念

兩種配置範式:從 JS 物件到 CSS 指令

上一篇《類別偵測機制》我們看懂了掃描器如何決定「哪些 class 會被產生」;本篇的配置系統則決定「這些 class 產生出什麼樣的值」——兩者是理解 Tailwind 運作的一體兩面。要談配置,得先看清 v3 與 v4 兩種範式的根本差異。

v3(JavaScript 配置)  vs  v4(CSS-first 配置)
══════════════════════════════════════════════════════════════

  v3:tailwind.config.js(獨立 JS 檔)
  ┌─────────────────────────────────┐
  │ // CSS 入口                     │
  │ @tailwind base;                 │
  │ @tailwind components;           │
  │ @tailwind utilities;            │
  │                                 │
  │ // tailwind.config.js           │
  │ module.exports = {              │
  │   content: [...],               │
  │   theme: { extend: {            │
  │     colors: { brand: '#...' }   │
  │   } },                          │
  │ }                               │
  └─────────────────────────────────┘

  v4:@theme(寫在 CSS 內)
  ┌─────────────────────────────────┐
  │ @import "tailwindcss";          │
  │                                 │
  │ @theme {                        │
  │   --color-brand: oklch(...);    │
  │ }                               │
  └─────────────────────────────────┘

兩者的差異不只是「檔案換了位置」,而是幾個關鍵面向的轉變:

面向v3(JavaScript)v4(CSS-first)
設定位置tailwind.config.jsCSS 檔內的 @theme 指令
引入方式@tailwind base/components/utilities@import "tailwindcss"
設計 TokenJS 物件(theme / extendCSS 變數(custom properties)
內容偵測content 陣列(glob)自動偵測 + @source
顏色格式預設十六進位(RGB/HSL)預設 OKLCH
主題值取用theme() 函式、resolveConfig直接用 var(--...)
舊配置相容可用 @config 載入舊 JS 配置

其中最值得記住的一列是「主題值取用」。在 v3,你若想在自訂 CSS 裡引用主題色,得透過 theme('colors.brand') 這種函式呼叫;在 v4,設計 Token 本身就是 CSS 變數,你直接 var(--color-brand) 就能用。「設定即變數」這個雙重身分,是 v4 一切便利的源頭。

順帶一提 @import "tailwindcss" 這一行到底做了什麼。它其實會展開成一組 @layer 宣告與三個子檔案的匯入,這也解釋了 v4 的樣式層順序:

/* @import "tailwindcss" 實際展開的內容(概念) */
@layer theme, base, components, utilities;
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/preflight.css" layer(base);
@import "tailwindcss/utilities.css" layer(utilities);

理解這一點的好處是:你會知道 @theme 定義的 Token 屬於最底層的 theme 層,而 utility 屬於最上層的 utilities 層——這個層級順序決定了誰能覆蓋誰。這也是為什麼 v4 不再需要 v3 那三行 @tailwind base/components/utilities:一行 @import 就把整套框架連同層級順序一起帶進來了。

另一個要留意的差異是顏色格式。v4 的預設調色盤改用 OKLCH 色彩空間,而非 v3 慣用的十六進位。OKLCH 的好處是「感知均勻」——調整亮度或彩度時,人眼感受到的變化更線性,做深淺色階(如 brand-50brand-950)時色階過渡更自然。你當然還是可以在 @theme 裡繼續用 #3b82f6 這種十六進位值,v4 完全接受;但若你要自建一整套色階,OKLCH 會省下很多手動微調的功夫。

@theme 的運作原理:一份定義,兩種產出

@theme 是 v4 配置的入口。它有一個容易被低估、卻極為關鍵的特性:你在裡面定義的每一個變數,都會同時產生兩種東西——一個 :root 上的 CSS 變數,以及一組對應的 utility class。

@theme → CSS 變數 + Utility Class(一份定義,兩種產出)
══════════════════════════════════════════════════════════════

  你撰寫的 @theme
  ┌─────────────────────────────────┐
  │ @theme {                        │
  │   --color-brand: oklch(...);    │
  │   --spacing-18: 4.5rem;         │
  │ }                               │
  └────────────────┬────────────────┘
                   │ Tailwind 處理
        ┌──────────┴──────────┐
        ▼                     ▼
  ┌──────────────┐   ┌────────────────────────┐
  │ 輸出到 :root │   │ 生成 utility class      │
  │              │   │                         │
  │ :root {      │   │ .bg-brand {             │
  │  --color-    │   │   background-color:     │
  │   brand:...; │   │   var(--color-brand);   │
  │  --spacing-  │   │ }                       │
  │   18:4.5rem; │   │ .p-18 { padding:4.5rem }│
  │ }            │   │ .m-18 { margin:4.5rem } │
  └──────────────┘   └────────────────────────┘
   可在任意 CSS       在 HTML 的 class 屬性中
   規則裡 var() 引用   直接使用

這個「一份定義、兩種產出」的設計解決了 v3 時代一個很煩人的重複問題。在 v3,如果你既想在 HTML 用 bg-brand,又想在某段自訂 CSS 裡引用同一個品牌色,往往得在 config 定義一次、再手動宣告一個 CSS 變數一次,兩處要同步維護。v4 讓你只寫一次 --color-brand,class 與變數就都有了,永遠不會不同步。

還有一個實務上的甜頭:因為 Token 現在是真正的 CSS 變數,它們會進到瀏覽器的執行環境。這代表你的 JavaScript 也能透過 getComputedStyle(document.documentElement).getPropertyValue('--color-brand') 讀到當前主題色——例如要把品牌色傳給一個 canvas 繪圖 API 或某個第三方圖表函式庫時,不必再把色值硬編一份在 JS 裡。設計 Token 從此有了單一事實來源(single source of truth),CSS、HTML class、JavaScript 三邊都指向同一個 @theme 定義。

命名空間規則:前綴決定生成哪類 utility

@theme 怎麼知道要為某個變數生成哪一類 utility?答案是看變數的命名空間前綴。這是整套配置系統最需要背下來的規則,因為它決定了你自訂的 Token 會變成什麼樣的 class。

CSS 變數前綴生成的 utility範例
--color-*顏色相關(bg-text-border- 等)--color-brandbg-brand
--spacing-*間距(p-m-w-h-gap- 等)--spacing-18p-18
--font-*字體族(font---font-displayfont-display
--font-size-*字體大小(text---font-size-herotext-hero
--font-weight-*字重(font---font-weight-heavyfont-heavy
--breakpoint-*響應式前綴--breakpoint-xsxs: 前綴
--radius-*圓角(rounded---radius-cardrounded-card
--shadow-*陰影(shadow---shadow-cardshadow-card
--ease-*過渡曲線(ease---ease-snappyease-snappy

理解這張表最好的方式,是抓住它的因果方向:不是「我想要一個 bg-brand,所以去某處註冊它」,而是「我定義了一個 --color-brand,於是 bg-brand(以及所有吃顏色的 utility)自動就有了」。你操作的是設計 Token這個源頭,utility 是它的自然衍生物。這也解釋了為什麼命名前綴不能亂取——--color- 這個前綴本身就是你對 Tailwind 的指令,告訴它「這是一個顏色,請生成顏色類的 utility」。

間距系統:單一 --spacing 推導全部

間距是命名空間規則裡一個特別優雅的例子,值得單獨拉出來講。v4 的間距系統由單一的 --spacing 基準變數推導出所有間距 utility,而不是逐一列舉每個值:

@theme {
  --spacing: 0.25rem;   /* 基準單位 */
}
/* 於是自動推導:
   p-1 = 0.25rem、p-2 = 0.5rem、p-4 = 1rem
   w-16 = 4rem(16 × 0.25rem)、mt-8 = 2rem(8 × 0.25rem) */

這帶來一個 v3 沒有的好處:任意倍數的間距不需事先設定就能用。在 v3,如果你想用 grid-cols-15w-17 這種非預設的值,得先在 config 的 spacing 裡加上對應的鍵;v4 因為所有間距都是 --spacing 的倍數,w-17mt-8gap-13 這類值開箱即用,掃描器掃到就自動算出來。這是「單一基準變數 + 乘法推導」設計換來的彈性。

實作範例

理論看完,我們來看實際的 CSS——先示範 @theme 定義各類 Token,再對照 extend 的差異,最後是 @config 相容與 v3 到 v4 的遷移。

定義設計 Token:一份完整的 @theme

以下是一份涵蓋顏色、間距、字體、斷點的 @theme 範例,展示各命名空間怎麼寫:

/* src/index.css */
@import "tailwindcss";

@theme {
  /* 顏色系統(v4 預設用 oklch 色彩空間,感知均勻) */
  --color-brand-50:  oklch(0.97 0.02 260);
  --color-brand-500: oklch(0.60 0.22 260);  /* → bg-brand-500、text-brand-500... */
  --color-brand-900: oklch(0.25 0.12 260);
  --color-surface:   oklch(0.99 0 0);       /* → bg-surface、text-surface... */

  /* 自訂間距(會生成 p-18、m-18、w-18、gap-18... 等所有間距 utility) */
  --spacing-18:  4.5rem;
  --spacing-128: 32rem;

  /* 字體族(→ font-display、font-mono) */
  --font-display: "Cal Sans", "Inter", sans-serif;
  --font-mono:    "JetBrains Mono", monospace;

  /* 字體大小(→ text-hero) */
  --font-size-hero: 3.5rem;

  /* 斷點(→ 新增 xs: 與 3xl: 響應式前綴,v4 斷點預設用 rem) */
  --breakpoint-xs:  30rem;   /* 480px */
  --breakpoint-3xl: 112rem;  /* 1792px */

  /* 圓角與陰影(→ rounded-card、shadow-card) */
  --radius-card: 0.75rem;
  --shadow-card: 0 1px 3px 0 rgb(0 0 0 / 0.1);
}

寫完之後,你在 HTML 裡就能直接用 bg-brand-500 p-18 font-display text-hero rounded-card xs:flex 3xl:grid-cols-6 這一整排 class,同時在任何自訂 CSS 規則裡用 var(--color-brand-500) 引用同一個值。

擴充 vs 覆寫:@theme extend 的分界

這是新手最容易踩雷的地方。不帶 extend@theme覆蓋你所觸及的命名空間,可能把 Tailwind 的預設值一起清掉;@theme extend保留預設並追加

/* ❌ 覆蓋:@theme 會清掉整個命名空間的預設值 */
@theme {
  --color-brand: oklch(0.6 0.24 260);
  /* 風險:若這樣做會影響 --color-* 命名空間,
     預設的 text-red-500、bg-green-600 等可能失效 */
}

/* ✅ 追加:@theme extend 保留所有預設,只新增 brand */
@theme extend {
  --color-brand: oklch(0.6 0.24 260);
  /* 預設的 text-red-500、bg-blue-600 全部保留,
     同時新增 bg-brand、text-brand */
}

日常擴充品牌色、加幾個自訂間距時,幾乎都該用 @theme extend。只有你確實想完全替換某個命名空間時,才用不帶 extend 的 @theme。v4 還提供 initial 關鍵字做更精確的清除:

@theme {
  /* 清空整個顏色命名空間,只保留下面自訂的三個 */
  --color-*: initial;
  --color-white:    #fff;
  --color-brand:    oklch(0.6 0.24 260);
  --color-midnight: oklch(0.2 0.08 260);
  /* 執行後只剩 bg-white、bg-brand、bg-midnight,不再有 bg-red-500 */
}

v3 差異:v3 的對應概念是 theme: { ... }(覆蓋)與 theme: { extend: { ... } }(追加)。v4 把這個「覆蓋 vs 追加」的選擇搬到了 @theme@theme extend 的差別上,心智模型是一致的,只是換了語法載體。

@theme 與 :root:一個常見混淆點

前面提過 @theme 的變數會生成 utility,而 :root 的不會。這在實務上是這樣體現的:

@import "tailwindcss";

@theme {
  --color-mint-500: oklch(0.72 0.15 165);
  /* ✅ 會產生 bg-mint-500、text-mint-500、border-mint-500... */
}

:root {
  --header-height: 64px;
  /* 只是普通 CSS 變數,不會產生任何 utility;
     供自訂 CSS 用,例如 height: calc(100vh - var(--header-height)) */
}

判斷準則很簡單:這個值需不需要當 class 用? 需要就放 @theme;只是內部 CSS 規則要引用的中間變數,放 :root 即可。另外要記得 @theme 內的變數必須是頂層、不可巢狀

@config:相容舊的 tailwind.config.js

遷移大型專案時,你不一定能一次把整份 tailwind.config.js 翻成 @theme。這時用 @config 指令把舊配置載回來,讓 JS 配置與 CSS 配置並存

/* src/index.css */
@import "tailwindcss";

/* 載入既有的 v3 JS 配置(遷移期橋接) */
@config "./tailwind.config.js";

/* 同時可以用 @theme extend 在舊配置之上追加新的 v4 Token */
@theme extend {
  --color-new-accent: oklch(0.65 0.18 150);
}

@config 是遷移期的橋樑:它讓你先把 Tailwind 升級到 v4(享受 Oxide 引擎與自動內容偵測),同時暫時保留還沒來得及轉換的 JS 配置,之後再逐步把 config 裡的內容搬進 @theme。這也是某些只支援 JS 配置的第三方外掛在 v4 下仍能運作的關鍵。

從 config.js 到 @theme:遷移思維對照

實際遷移時,把 JS 物件的巢狀結構「攤平」成 CSS 變數即可。對照如下:

// Before(v3)tailwind.config.js
module.exports = {
  content: ['./src/**/*.{html,jsx,tsx}'],
  theme: {
    extend: {
      colors: { brand: { 500: '#3b82f6' }, surface: '#fafafa' },
      spacing: { 18: '4.5rem' },
      fontFamily: { display: ['Cal Sans', 'Inter', 'sans-serif'] },
      screens: { xs: '480px' },
    },
  },
  plugins: [require('@tailwindcss/typography')],
}
/* After(v4)src/index.css */
@import "tailwindcss";
@plugin "@tailwindcss/typography";   /* require() → @plugin 指令 */

@theme extend {
  --color-brand-500: #3b82f6;        /* colors.brand.500 → 扁平變數 */
  --color-surface:   #fafafa;
  --spacing-18:      4.5rem;          /* spacing.18 → --spacing-18 */
  --font-display:    "Cal Sans", "Inter", sans-serif;  /* fontFamily.display */
  --breakpoint-xs:   30rem;           /* screens.xs(480px → 30rem) */
}

遷移思維的幾個重點:

  • 不需要 content 陣列:v4 自動內容偵測,掃描範圍不用手動維護(上一篇《類別偵測機制》已詳述)。
  • require() 外掛改用 @plugin 指令:直接寫在 CSS 裡。
  • JS 物件巢狀 → 扁平 CSS 變數colors.brand.500 變成 --color-brand-500,用連字號攤平層級。
  • 斷點單位改 rem:v4 斷點預設用 rem480px 寫成 30rem

常見錯誤與最佳實踐

最容易踩的坑

坑一(頭號殺手):該用 extend 卻用了純 @theme,把預設值清掉。 你只是想加一個品牌色,結果整個 --color-* 命名空間被覆蓋,bg-gray-100text-red-500 突然全失效。日常擴充一律用 @theme extend,除非你明確要替換整個命名空間。

坑二:把想當 class 用的值誤放進 :root:root 定義 --color-brand 只會得到一個 CSS 變數,bg-brand 不會出現。要生成 utility 就得放進 @theme

坑三:@theme 內變數巢狀或非頂層。 @theme 的變數必須是頂層宣告,不能像一般 CSS 那樣包在選擇器裡。若需要依主題切換值,做法是在 @theme 定義基準 Token,再到 @layer base 裡用 .dark 等選擇器覆蓋 CSS 變數:

@theme {
  --color-bg: oklch(0.99 0 0);
  --color-fg: oklch(0.15 0 0);
}

@layer base {
  .dark {
    --color-bg: oklch(0.15 0 0);   /* 覆蓋變數值即可切主題 */
    --color-fg: oklch(0.95 0 0);
  }
}

坑四:以為 v4 完全不能用 JS 配置。 不是的——@config 讓舊的 tailwind.config.js 仍可載入,遷移不必一步到位。

最佳實踐

  • extendextend:保留 Tailwind 完整的預設設計系統,只追加你需要的,避免無意間破壞既有 class。
  • 善用「設定即變數」:需要在自訂 CSS 引用主題值時,直接 var(--color-brand),不要再另外宣告一份重複的變數。
  • 語義化命名 Token:除了 --color-brand-500 這類原始色,可再定義 --color-primary: var(--color-brand-500) 這種語義層,日後換色只改一處。
  • 遷移分階段:先用 @config 橋接讓專案跑在 v4 上,再把 config 內容逐塊搬進 @theme,最後移除 @config

一句話總結:日常擴充用 @theme extend、想當 class 的值放 @theme、內部變數放 :root、遷移期靠 @config 橋接。

小結

這是 TailwindCSS 完整教學 系列的第四篇。上一篇《類別偵測機制》我們看懂了掃描器如何決定「哪些 class 會被產生」,這一篇則深入配置系統——決定「這些 class 產生出什麼樣的值」。回顧幾個重點:

  • v4 是 CSS-first 配置。 設定從 v3 的 tailwind.config.js 搬進 CSS 的 @theme 指令,並用 @import "tailwindcss" 取代 @tailwind 三行。
  • 一份定義、兩種產出。 @theme 裡的每個變數同時是 :root 上的 CSS 變數,又依命名空間前綴生成對應 utility——這也是它與只產生變數的 :root 的關鍵差異。
  • 命名空間前綴決定 utility 類別--color-*bg-/text---spacing-*p-/m---font-*font---breakpoint-* → 響應式前綴;而 --spacing 單一基準變數更能推導任意倍數間距。
  • 擴充 vs 覆寫:日常一律用 @theme extend 保留預設並追加,initial 可精確清空;純 @theme 才是覆蓋。
  • @config 是遷移橋樑:讓舊的 JS 配置與新的 @theme 並存,遷移思維就是把 JS 巢狀物件攤平成扁平 CSS 變數。

理解了配置系統,你就掌握了 Tailwind「值從哪裡來」的全貌。下一篇 《TailwindCSS 指令與函式》,我們將深入 @import@theme@layer@apply@source@plugin@config 這些指令,以及 theme()--alpha()--spacing() 等函式的用法——配置定義了設計 Token,而指令與函式則是你在 CSS 裡真正「動用」這些 Token 的工具,兩者合起來構成 v4 CSS-first 的完整語彙。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。

本週主打

AI 自動化入門包

你每天手動在做的那些煩事,其實 AI 可以自己跑。這份給你 10 個照著做就會的自動化工作流 + 50 個複製即用的提示詞,不用會寫程式。

看看這個產品 →