TailwindCSS v4 遷移指南:從 v3 升級的破壞性變更、@tailwindcss/upgrade 自動遷移與漸進策略完整教學 | TailwindCSS 完整教學
TailwindCSS v4 是一次根本性的架構重設計:設定從 JavaScript 搬進 CSS、核心引擎用 Rust 重寫、
@tailwind三行換成一行@import,還有一整批 utility 被系統性重新命名。這些破壞性變更(breaking changes)讓從 v3 直接升級不是「換個版本號」那麼簡單。這篇是 TailwindCSS 完整教學 系列的第六篇,我們把前幾篇散落的 v3/v4 差異收攏起來,帶你走一遍完整的 v4 遷移 流程——從自動遷移工具npx @tailwindcss/upgrade、逐項破壞性變更對照,到大型專案的漸進升級策略。
前言
遷移(migration) 指的是把一個既有專案從 TailwindCSS v3 升級到 v4 的完整過程——不只是更新套件版本,還包含改寫設定檔、替換棄用語法、修復預設值變化,以及驗證畫面沒有跑掉。TailwindCSS v4 的變更幅度是歷來最大的一次,所以「遷移」在這裡是一件需要方法的事,而不是 npm update 一鍵完成。
用一個現實世界的類比來理解:把 v3 升級到 v4,就像搬家到一間格局大改的新房子。地址(TailwindCSS)沒變、你的家具(HTML 結構、大部分 class)也大多能帶過去,但新房子的電路配置改了(設定從 JS 搬到 CSS)、開關位置換了(@tailwind 換成 @import)、有幾件家具的尺寸標籤重貼過(shadow-sm 現在叫 shadow-xs),而且新房子只收現代的插頭(需要較新的瀏覽器)。搬家公司(@tailwindcss/upgrade)能幫你把大件家具搬過去、貼好新標籤,但有些精細的東西——牆上的畫該掛哪、燈光色溫對不對——還是得你親自到現場比對。這篇就是你的搬家清單。
本篇你將學到:
- 四大破壞性變更:設定檔改 CSS-first、
@tailwind→@import、utility 系統性重命名、工具鏈與預設值變化,一張總覽表看懂全貌 - 自動遷移工具:
npx @tailwindcss/upgrade幫你做什麼、做不到什麼,以及執行前後的注意事項 - 常見遷移坑:邊框變黑、focus ring 變細、陰影偏移這些「升級後畫面跑掉」的真兇與解法
- 漸進遷移策略:大型專案如何用
@config相容指令分階段遷移,把風險攤平
本系列以 TailwindCSS v4 為預設版本,本篇會明確標註每一項與 v3 的差異。
核心概念
v3 → v4 四大變更區塊
在動手之前,先建立一張全景地圖。v4 的破壞性變更可以歸納成四個區塊:配置、指令、utility 命名、工具鏈與預設值。下面這張表把每個區塊的核心變化並列出來:
| 區塊 | v3 做法 | v4 做法 |
|---|---|---|
| 配置 | tailwind.config.js(JS 物件) | CSS 裡的 @theme { } |
| 內容偵測 | content: [...] 手動列出 | 自動偵測(免設定) |
| 外掛 | plugins: [require(...)] | @plugin "..." |
| 自訂 Token | theme.extend.colors.brand | --color-brand: ... |
| 指令 | @tailwind base/components/utilities | @import "tailwindcss" |
theme() 語法 | theme('colors.blue.500') | var(--color-blue-500) 或 theme(--color-blue-500) |
screen() | screen('md') | theme(--breakpoint-md) |
| utility 命名 | shadow-sm、blur-sm、rounded-sm | shadow-xs、blur-xs、rounded-xs(整體 shift) |
| 漸層 | bg-gradient-to-r | bg-linear-to-r |
| 透明度 | bg-opacity-50 | bg-black/50(斜線語法) |
| 工具鏈 | tailwindcss(PostCSS 外掛) | @tailwindcss/postcss |
| autoprefixer | 需要 | 不再需要(Lightning CSS 內建) |
| Vite 整合 | 透過 PostCSS | @tailwindcss/vite(原生) |
這張表最需要先建立的認知是:v4 的變更不是零散的、而是有一條主軸——「CSS-first」。前面《配置系統》與《指令與函式》兩篇其實已經在鋪這條路:設定搬進 CSS(@theme)、擴充能力搬進 CSS(@utility、@custom-variant)。這一篇的遷移,本質上就是把一個「以 JS 為配置中心」的專案,翻譯成「以 CSS 為配置中心」的專案。理解了這條主軸,很多變更就不再是需要死背的規則,而是同一個方向的必然結果。
值得多花一點篇幅說明「為什麼要做這麼大的架構轉移」,因為理解動機能讓你在遷移時心裡有底、少踩坑。v3 的 JS 配置有三個結構性的痛點。第一,JS 與 CSS 之間有一道牆。 tailwind.config.js 裡定義的 Token 會被拿去生成 utility class,但這些值沒辦法直接在你手寫的 CSS 規則裡引用——你得透過 theme() 函式這座橋才能取用。v4 把 Token 本身變成真正的 CSS 變數,var(--color-brand) 直接就能用,牆被拆掉了。第二,Plugin API 太重。 v3 要擴充一個 utility 或變體,得學 addUtilities()、addComponents()、matchUtilities()、addVariant() 這一整套 JS API;v4 多數場景改用 @utility、@custom-variant 在 CSS 裡幾行就搞定。第三,認知分裂。 設計 Token 在 JS 檔、樣式在 CSS 檔,你在瀏覽器 DevTools 看到的是 CSS 變數,但要搞懂它從哪來卻得跳回去翻 JS 設定。v4 讓 Token 就定義在 CSS 裡,DevTools 直接可見,這條追查鏈變短了。
把這三點合起來看,v4 的方向其實是「盡量貼近原生 CSS 標準」。隨著瀏覽器原生的 @layer、CSS 變數、@property、@scope、CSS Nesting 逐一成熟,Tailwind 能把越來越多原本要靠自家引擎硬幹的事,交還給瀏覽器本身。這也是為什麼 v4 敢設下較高的瀏覽器門檻——它是在賭「未來」,用現代 CSS 特性換取更透明、更少魔法的架構。你在遷移時遇到的每一個破壞性變更,幾乎都能回溯到這個「向 CSS 標準靠攏」的初衷。
破壞性變更詳解:三種「會讓畫面跑掉」的變化
四大區塊裡,配置與指令的變更是「不改就 build 不起來」,錯得明顯、容易發現;真正陰險的是那些「build 成功、但畫面悄悄變了」的破壞性變更。這類問題分三種,值得單獨拆開講。
第一種:utility 系統性重命名(shift down 模式)。 v4 對陰影、模糊、圓角做了系統性的名稱位移——原本的 sm 縮小為 xs,騰出 sm 給視覺上更合理的尺度:
| v3 class | v4 class | 視覺效果 |
|---|---|---|
shadow-sm | shadow-xs | 相同(只是改名) |
shadow | shadow-sm | 相同 |
shadow-md / -lg / -xl | 不變 | 相同 |
blur-sm | blur-xs | 相同 |
blur | blur-sm | 相同 |
rounded-sm | rounded-xs | 相同 |
rounded | rounded-sm | 相同 |
ring | ring-3 | 明確化為 3px(見下) |
關鍵在於:視覺效果沒變,是名稱平移了。所以如果你升級後沒改名,shadow-sm 會突然套用到「原本 v3 shadow 的較大陰影」,設計稿對齊的陰影就會整批偏移。
為什麼 v4 要做這種看似多餘的改名?背後的設計考量是「讓尺度命名更一致、更符合直覺」。在 v3,sm(small)其實是最小的一級,但直覺上「small」應該還有比它更小的空間;同時 shadow(無後綴)這種「裸名」在整個尺度系統裡也顯得突兀——它到底該排在 sm 前面還是後面,沒有明說。v4 藉這次大版本一次把它理順:引入 xs(extra-small)當最小級,讓 sm、md、lg、xl 形成一條前後一致、沒有裸名的完整尺度。代價就是這次遷移得整批改名,但換來的是往後新增尺度時不會再有命名上的尷尬。這也是為什麼這類改名值得交給 @tailwindcss/upgrade 自動處理——它是純機械性的字串替換,人工做既枯燥又容易漏。
第二種:預設值變更。 這是最容易被忽略、也最讓人抓狂的一類,因為 class 名稱一字沒改,但預設行為變了:
| 項目 | v3 預設 | v4 預設 | 影響 |
|---|---|---|---|
border(不指定色) | gray-200 | currentColor | 邊框顏色整批改變 |
ring 寬度 | 3px | 1px | focus ring 變細 |
ring 顏色 | blue-500/50(半透明藍) | currentColor | focus ring 變成文字色 |
也就是說,一個 v3 寫著 <div class="border"> 的元素,在 v4 邊框顏色會從淺灰變成「繼承自文字的深色」;一個 <input class="focus:ring"> 的輸入框,focus 時的光暈會從 3px 藍色變成 1px 深色。這些都不會報錯,只會在你回頭看畫面時覺得「怪怪的」。
第三種:語法層級的破壞性變更。 這些是遷移時最容易漏掉的細節,因為它們藏在任意值與修飾子的寫法裡:
| 項目 | v3 | v4 |
|---|---|---|
| CSS 變數任意值 | bg-[--brand] | bg-(--brand) |
| 任意值逗號分隔 | grid-cols-[max-content,auto] | grid-cols-[max-content_auto](改空格) |
important 位置 | !flex(前綴) | flex!(後綴) |
| variant 堆疊順序 | 右到左 first:*:pt-0 | 左到右 *:first:pt-0 |
| 關閉 outline | outline-none | outline-hidden |
還有兩個容易遺漏的行為變更:其一,v4 的 hover: 只在支援 hover 的裝置生效(包在 @media (hover: hover) 裡),解決觸控裝置的「黏著 hover」問題,但也意味著你若依賴 hover 在觸控裝置生效的設計要重新檢視;其二,v4 不再支援 Sass / Less / Stylus——因為 v4 本身就是預處理器,不再設計與其他 CSS 前處理器搭配使用。
瀏覽器需求:v4 的入場門檻
v4 之所以能把架構做得這麼「CSS 原生」,代價是它大量依賴現代 CSS 特性(Cascade Layers、@property、color-mix() 等)。因此 v4 有明確的最低瀏覽器需求:Safari 16.4+、Chrome 111+、Firefox 128+。
這件事在遷移決策上很重要:如果你的產品目標瀏覽器矩陣落在這個範圍內(絕大多數 2023 年後的瀏覽器都符合),儘管升級;但若你仍需支援更舊的環境,官方建議繼續使用 v3.4,它會持續維護。換句話說,遷移的第一步不是動手改 code,而是先確認「我的使用者用的瀏覽器版本,v4 吃得下嗎」。
實務上怎麼確認?最可靠的方法是打開你的網站分析(例如 Google Analytics 或 Search Console)的瀏覽器與版本分佈報表,看看實際造訪者的瀏覽器版本落點。如果 v4 不支援的舊瀏覽器佔比極低(例如低於千分之一),那多半可以接受;但如果你的受眾包含大量企業內網、政府單位或使用老舊裝置的族群,這個比例可能高得驚人,貿然升級 v4 會讓這部分使用者看到破版的畫面。這也是為什麼「先看數據再決定遷移時機」比「跟風升級最新版」更負責任——版本號不是越新越好,適合你的使用者才是關鍵。
實作範例
理論看完,我們來實際走一遍遷移。以下先示範自動遷移工具,再看逐步的手動遷移前後對照。
首選:npx @tailwindcss/upgrade 自動遷移
官方的升級 CLI 是遷移的第一站,多數專案跑完它就完成八成的工作。執行前務必先開一個乾淨的 git branch,方便事後比對 diff:
# 前置需求:Node.js 20 以上
# 建議先切到乾淨的 branch
git checkout -b chore/upgrade-tailwind-v4
# 執行官方自動遷移工具
npx @tailwindcss/upgrade
這支工具會自動幫你處理以下項目:
# @tailwindcss/upgrade 自動處理的項目:
# @tailwind 三行 → @import "tailwindcss"
# tailwind.config.js → CSS 的 @theme(基本轉換)
# postcss.config.js → @tailwindcss/postcss
# utility 重命名 → shadow-sm→shadow-xs、blur-sm→blur-xs …
# theme() 語法 → 點記法字串改為 CSS 變數名
# screen('md') → theme(--breakpoint-md)
# 仍需你「手動收尾」的項目:
# 複雜的 JS 外掛邏輯
# 含字串插值的動態 theme() 用法
# JS 配置中用到函式/運算的部分
# 預設值變更(border 顏色、ring 寬度)的「視覺」修復
跑完之後,一定要人工檢查 diff、並實際打開畫面比對——工具能改語法,但改不動那些「語法對、但視覺變了」的預設值差異。
手動遷移前後對照
若你想理解工具到底做了什麼,或有些地方需要手動處理,下面是核心檔案的 v3 → v4 對照。
步驟一:更新依賴。 先卸載 v3 相關套件,裝上 v4:
# 移除 v3
npm uninstall tailwindcss autoprefixer
# 安裝 v4
npm install tailwindcss@latest
# 依你的建構工具三選一:
npm install @tailwindcss/vite # Vite 專案(推薦,效能最好)
npm install @tailwindcss/postcss # PostCSS / Webpack / Next.js
npm install @tailwindcss/cli # 零依賴 CLI
步驟二:更新 CSS 入口。 這是最核心、也最不能漏的一步:
/* ❌ Before(v3)*/
@tailwind base;
@tailwind components;
@tailwind utilities;
/* ✅ After(v4):一行取代三行 */
@import "tailwindcss";
步驟三:把 tailwind.config.js 搬進 @theme。 v3 的 JS 設定物件,在 v4 變成 CSS 裡的 @theme 區塊,每個 Token 都是一個 CSS 變數:
/* ✅ After(v4):src/index.css */
@import "tailwindcss";
/* 外掛:對應 v3 的 plugins: [require(...)] */
@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms";
/* 自訂 Token:對應 v3 的 theme.extend */
@theme {
/* 顏色:theme.extend.colors.brand → --color-brand-* */
--color-brand-50: #eff6ff;
--color-brand-500: #3b82f6;
--color-brand-950: #172554;
/* 間距:theme.extend.spacing.18 → --spacing-18 */
--spacing-18: 4.5rem;
/* 字體:theme.extend.fontFamily.display → --font-family-display */
--font-family-display: "Cal Sans", "Inter", sans-serif;
/* 斷點:注意 v4 斷點用 rem,不用 px(480px ÷ 16 = 30rem)*/
--breakpoint-xs: 30rem;
--breakpoint-3xl: 112rem;
}
/* Dark mode:對應 v3 的 darkMode: 'class' */
@custom-variant dark (&:where(.dark, .dark *));
步驟四:更新 PostCSS 設定(若用 PostCSS)。套件名稱換了,autoprefixer 也可以移除:
/* ❌ Before(v3):postcss.config.js */
module.exports = {
plugins: {
tailwindcss: {}, // 舊套件名稱
autoprefixer: {}, // v4 不再需要
},
}
/* ✅ After(v4)*/
module.exports = {
plugins: {
"@tailwindcss/postcss": {}, // 新套件名稱,Lightning CSS 已內建 prefix
},
}
或者,改用效能更好的 Vite 原生外掛:
/* vite.config.js */
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [tailwindcss()],
});
驗證遷移完整性
改完之後,用幾條 grep 快速掃出殘留的 v3 語法,確認沒有漏網之魚:
# 應該都「查無結果」——若有結果代表還沒改乾淨
grep -r "@tailwind " src/ # 應改為 @import "tailwindcss"
grep -r "screen('" src/ # 應改為 theme(--breakpoint-*)
grep -r "overflow-ellipsis" src/ # 應改為 text-ellipsis
grep -r "flex-grow" src/ # 應改為 grow
# 這些需要「人工確認」是否要改名(視覺 shift)
grep -r "shadow-sm" src/ # 確認是否應改為 shadow-xs
grep -r "blur-sm" src/ # 確認是否應改為 blur-xs
grep -r "rounded-sm" src/ # 確認是否應改為 rounded-xs
常見錯誤與最佳實踐
五個最容易踩的遷移坑
坑一:@tailwind 指令無效、樣式完全不生效。 這是最常見的第一個障礙。症狀是 CSS 出現 @tailwind 警告、或整個網站沒有任何 Tailwind 樣式。原因是 v4 已移除 @tailwind 指令,解法是把三行換成一行 @import "tailwindcss"。
坑二:Cannot find module 'tailwindcss'(build 失敗)。 這是第二個障礙。原因是 postcss.config.js 還用舊的外掛名稱 tailwindcss。解法是改成 @tailwindcss/postcss 並確認已 npm install @tailwindcss/postcss。
坑三:邊框顏色意外變深。 這是「預設值變更」的頭號受害者。症狀是 <div class="border"> 的邊框從淺灰變成黑色或深色,原因是 v4 的 border 預設色從 gray-200 改為 currentColor。有兩種修法:
/* 方案 A:在每個 border 後明確指定顏色 */
/* v3: <div class="border"> → v4: <div class="border border-gray-200"> */
/* 方案 B:在 @layer base 全域還原 v3 行為 */
@layer base {
*, ::before, ::after {
--tw-default-border-color: var(--color-gray-200);
}
}
坑四:focus ring 變細、變色。 症狀是表單 focus 樣式從 3px 藍色光暈變成 1px 深色細線,原因是 ring 預設寬度從 3px → 1px、顏色從 blue-500/50 → currentColor。修法:
/* 逐一修:v3 <input class="focus:ring">
→ v4 <input class="focus:ring-3 focus:ring-blue-500/50"> */
/* 或全域還原 */
@layer base {
:root {
--tw-ring-color: var(--color-blue-500) / 50%;
}
}
坑五:陰影大小整批偏移。 症狀是設計稿對齊的陰影看起來「大了一號」,原因就是前面說的 shift 模式——v4 的 shadow-sm 對應的是 v3 的 shadow。解法是系統性替換:shadow-sm → shadow-xs、shadow → shadow-sm(shadow-md 以上不變),blur 與 rounded 同理。這一步交給 @tailwindcss/upgrade 最省事。
大型專案的漸進遷移策略
小專案可以一次全部升級,但超過上百個元件的大型專案,一口氣改完風險太高。這時可以用 @config 相容指令把遷移拆成四個階段,讓風險攤平:
/* Phase 1 的關鍵:用 @config 讓「v4 引擎」暫時吃「v3 設定」*/
@import "tailwindcss";
@config "./tailwind.config.js"; /* 暫時保留舊設定,之後再刪 */
四階段策略如下:
- Phase 1 — 工具鏈切換(1–2 天):更新依賴、CSS 入口換
@import,並用@config指令暫時保留tailwind.config.js。目標是「v4 引擎 + v3 設定」能並存、build 不報錯。 - Phase 2 — 配置遷移(2–5 天):把
theme.extend逐項轉成@theme、plugins轉成@plugin,測試每個 Token 的 utility 是否正確生成,完成後刪掉tailwind.config.js與@config。 - Phase 3 — utility 重命名(自動化,0.5 天):跑
npx @tailwindcss/upgrade,手動確認 shadow / blur / rounded 的視覺,並修復 border / ring 預設值。 - Phase 4 — 全面測試(2–5 天):視覺回歸測試(逐頁截圖比對)、互動狀態測試(hover、focus、dark mode)、跨瀏覽器測試。
最佳實踐清單
- 先確認瀏覽器需求:目標矩陣不在 Safari 16.4+ / Chrome 111+ / Firefox 128+ 範圍內,就留在 v3.4。
- 一律先跑
@tailwindcss/upgrade:在乾淨 branch 上執行,讓工具做掉八成的機械性替換。 - 重點盯「預設值變更」:border、ring 這類工具改不動的視覺差異,一定要人工比對畫面。
- 大型專案用
@config分階段:先讓 v4 引擎吃 v3 設定,再逐步搬家,別一次改完。 - 收尾一定做視覺回歸測試:用 Playwright、Chromatic 或 Percy 截圖比對,抓出肉眼容易漏的偏移。
小結
這是 TailwindCSS 完整教學 系列的第六篇,也是 TW-1「核心概念」段落的收尾。上一篇《指令與函式》我們拆解了 v4 在 CSS 裡的完整語彙——@import、@layer、@apply、@utility、@custom-variant 與 theme()/--alpha()/--spacing();這一篇則把前面幾篇散落的 v3/v4 差異全部收攏,實際走了一遍從 v3 升級到 v4 的完整流程。回顧幾個重點:
- 四大變更區塊:配置改 CSS-first(
@theme)、指令換@import、utility 系統性重命名(shift 模式)、工具鏈換@tailwindcss/postcss——一條「CSS-first」主軸貫穿全部。 - 三種會讓畫面跑掉的變更:utility 重命名、預設值變更(border 變
currentColor、ring 變 1px)、語法層級變更(任意值括號、important後綴)——其中「預設值變更」最陰險,因為它不報錯。 - 自動遷移工具:
npx @tailwindcss/upgrade(需 Node.js 20+)做掉八成機械性工作,但預設值的視覺修復必須人工收尾。 - 瀏覽器需求:v4 需要 Safari 16.4+ / Chrome 111+ / Firefox 128+,需支援更舊環境就續用 v3.4。
- 漸進策略:大型專案用
@config相容指令,把遷移拆成工具鏈、配置、重命名、測試四個階段。
到這裡,TW-1 核心概念系列——從 Utility-First 哲學、安裝設定、配置系統、指令與函式,到這篇 v4 遷移——已經完整鋪好了 v4 的地基。下一篇 《TailwindCSS 排版與文字》,我們會正式進入 utility 的實戰,從最日常的字型、字級、行高、字距與文字對齊開始,帶你把設計系統的 Token 真正用在畫面上。