任意值語法(Arbitrary Values)——[] 詳解與範例
Tailwind 的 JIT 引擎支援以方括號 [] 直接嵌入任意 CSS 值,讓你在完全不修改設定檔的前提下即時對應設計稿的特殊數值。這是開發過程中最靈活的「逃生艙口(escape hatch)」,幾乎所有 utility 都支援此語法,涵蓋顏色、尺寸、CSS 函式,甚至完整的 CSS 屬性宣告。底線 _ 在任意值中會自動轉換為空白字元,若需保留真實底線則以 \_ 跳脫。
<!-- 任意寬高與固定像素 -->
<div class="w-[350px] h-[200px] min-h-[calc(100vh-4rem)]">
<!-- 任意顏色(背景、文字、邊框、漸層)-->
<div class="bg-[#1a1a2e] text-[#e9eefc] border-[#4a9eda]
from-[#0f172a] to-[#1e293b]">
<!-- 任意字體大小 + 行高(同時指定)-->
<p class="text-[13px]/[1.8]">精確控制字型</p>
<!-- 任意 CSS 屬性:方括號內為完整宣告,底線代空白 -->
<div class="[mask-image:linear-gradient(to_bottom,black,transparent)]">
<div class="[scroll-snap-type:x_mandatory] [scroll-padding:0_1rem]">
<!-- 任意響應式媒體查詢 -->
<div class="[@media(min-width:900px)]:flex hidden">
<!-- 任意偽類選擇器(& 代指目前元素)-->
<li class="[&:nth-child(3)]:font-bold [&:last-child]:border-0">
<!-- 父元素選擇器語法:在 .sidebar 內才隱藏 -->
<div class="[.sidebar_&]:hidden [.modal_&]:block">
<!-- CSS 變數作為任意值 -->
<div class="bg-[var(--brand-color)] text-[length:var(--font-hero)]">
<!-- 任意 grid-template(逗號用底線)-->
<div class="grid-cols-[1fr_2fr_1fr] grid-rows-[auto_1fr_auto]">
<!-- 任意 clip-path 與 filter -->
<div class="[clip-path:polygon(0_0,100%_0,100%_80%,0_100%)]
[filter:drop-shadow(0_4px_6px_rgba(0,0,0,0.3))]">
theme.extend,以維持設計 Token 的一致性,讓日後全域修改只需改一個地方,徹底避免散落各檔案的硬編碼數值。
底線轉換規則:任意值中的底線 _ 自動轉換為空白,例如 [margin:1rem_2rem] 等同 CSS 的 margin: 1rem 2rem。若需要真正的底線字元(如在 URL 或字串中),使用 \_ 跳脫。此規則同樣適用於任意屬性語法(方括號包住完整 CSS 宣告)與任意 grid-template 值,以及任意選擇器語法中的空格分隔符。
JIT 模式原理說明
Tailwind CSS v3 起,JIT(Just-In-Time)模式已成為唯一預設引擎,完全取代了舊版基於 PurgeCSS 的後處理流程。JIT 在開發時依照原始碼中實際出現的 class 字串即時產生對應的 CSS,而非預先產生數萬個 class 的完整組合。理解其工作原理,是避開常見陷阱的關鍵所在。
- 靜態字串掃描,非 JS 執行:JIT 使用正規表達式掃描 content 路徑下的所有檔案,尋找符合 Tailwind class 格式的完整字串,不執行任何 JavaScript。
- 開發環境體積極小:僅輸出你實際使用的 class,開發時 CSS 通常不超過數十 KB,對比舊版在開發環境動輒 5–10 MB 的完整輸出,差異懸殊。
- 任意值無限擴展:因為是即時生成,
[]語法可無限使用,不需預先定義,設計師提出的任何特殊數值都能立即回應。 - 所有 variant 自由組合:如
hover:focus:dark:lg:text-blue-500無需在 config 中預先啟用,JIT 掃描到完整字串就會輸出對應 CSS。 - 毫秒級增量編譯:只重新分析並輸出變動的部分,HMR(熱更新)幾乎感覺不到延遲,即使大型專案也能保持流暢的開發體驗。
// tailwind.config.js — content 路徑決定 JIT 靜態分析範圍
module.exports = {
content: [
'./src/**/*.{html,js,jsx,ts,tsx,vue}',
'./components/**/*.{html,js}',
'./pages/**/*.{js,ts,jsx,tsx}',
// 動態產生 class 的設定檔也要納入掃描
'./config/theme.js',
],
// ...
}
`text-${color}-500` 這類模板字串,JIT 靜態分析無法識別「完整字串」,所以這些 class 不會被輸出到 CSS,頁面樣式將靜默消失。正確做法是建立完整字串對照表:const colorMap = { red: 'text-red-500', blue: 'text-blue-500' },或將需要保留的 class 加入 safelist 設定中。
JIT 的靜態分析器不執行 JavaScript,只是逐字符掃描原始碼。因此即使 class 出現在 JS 的字串常數、Vue template 的靜態屬性、甚至 HTML 的 <!-- 註解 --> 中,只要是完整的 class 字串,JIT 都能識別並輸出。可善用此特性在 JS 模組頂部以陣列或物件預存所有可能用到的完整 class 名稱,集中管理動態 class 的對照表。
@apply 使用時機與陷阱——過早抽象的問題
@apply 允許在 CSS 檔案中將一組 Tailwind utility class 提取為語意化的元件 class,解決在 Jinja、Blade、PHP 等非元件化模板中反覆重寫相同 class 的問題。然而它是雙刃劍:使用不當會讓你重回傳統 CSS 的維護困境,並失去 Tailwind 的核心優勢——直接在 HTML 就能看到元素的完整樣式,不必來回查閱 CSS 與 HTML 兩個檔案。
過早抽象(Premature Abstraction)是 @apply 最常見的誤用模式:在元件只出現一兩次時就急於建立 CSS class,導致 CSS 檔案膨脹,且這些 class 日後幾乎不再複用,反而增加維護負擔。Tailwind 官方作者 Adam Wathan 明確建議:只有在確實無法使用元件框架抽象時,才考慮 @apply。
/* ✅ 適合使用 @apply:反覆出現的 UI 基礎元件(跨多個非元件化頁面)*/
@layer components {
.btn {
@apply inline-flex items-center justify-center;
@apply px-4 py-2 rounded-lg font-semibold text-sm;
@apply transition duration-150 cursor-pointer select-none;
@apply focus:outline-none focus:ring-2 focus:ring-offset-2;
}
.btn-primary {
@apply btn bg-blue-500 text-white;
@apply hover:bg-blue-600 focus:ring-blue-500;
@apply disabled:opacity-50 disabled:cursor-not-allowed;
}
.btn-secondary {
@apply btn bg-gray-100 text-gray-800;
@apply hover:bg-gray-200 focus:ring-gray-400;
}
/* 卡片元件 */
.card {
@apply bg-white dark:bg-gray-800 rounded-xl shadow-md overflow-hidden;
@apply border border-gray-100 dark:border-gray-700;
@apply transition hover:shadow-lg;
}
/* 表單輸入框 */
.input {
@apply w-full px-3 py-2 rounded-md border border-gray-300;
@apply focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent;
@apply dark:bg-gray-700 dark:border-gray-600 dark:text-white;
@apply placeholder:text-gray-400;
}
/* 徽章基底 + 語意化變體 */
.badge { @apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium; }
.badge-success { @apply badge bg-green-100 text-green-800; }
.badge-error { @apply badge bg-red-100 text-red-800; }
.badge-warning { @apply badge bg-yellow-100 text-yellow-800; }
}
- 只出現一兩次的樣式——直接在 HTML 寫 class 更快、更直觀、更易追蹤,無需翻找 CSS 檔案
- 你正在使用 React、Vue 或 Svelte——優先用元件抽象,元件複用比 CSS class 複用更有彈性也更易測試
- 複雜頁面佈局——使用元件抽象或 CSS Grid 原生語法更清晰,@apply 無法表達動態 class 邏輯
- 在
@layer base以外的地方對原生 HTML 標籤使用 @apply——容易造成 CSS 特異性(specificity)衝突,讓 utility class 難以覆蓋基底樣式 - 試圖用 @apply 重現整個頁面的佈局結構——這是濫用,會讓 Tailwind 失去所有可讀性優勢,本質上退化為傳統 BEM 的寫法
- 在第三方 UI 函式庫(如 shadcn/ui、daisyUI)的元件內部使用 @apply——可能造成難以排查的樣式衝突與版本升級問題
@apply 合併的 class,其最終 CSS 特異性取決於 @layer 位置,而非原始 utility 的特異性。將 @apply 放在 @layer components 內確保它在 utilities 之前,讓個別 utility class 仍可在 HTML 端覆蓋元件樣式。若直接在 @layer 外使用 @apply,產生的 CSS 特異性可能意外高於 utilities,導致難以覆蓋的樣式問題。
自訂插件開發基本範例
Tailwind 的插件系統允許你以 JavaScript 函式擴充框架,新增自訂 utilities、components 或 variants,並可將其打包為 npm 套件發布共享。插件 API 提供 addUtilities、addComponents、addBase、addVariant 四個核心方法,覆蓋幾乎所有自訂需求。
# 安裝官方插件 npm install -D @tailwindcss/forms npm install -D @tailwindcss/typography npm install -D @tailwindcss/aspect-ratio npm install -D @tailwindcss/container-queries
// tailwind.config.js — 官方插件 + 自訂插件完整範例
const plugin = require('tailwindcss/plugin')
module.exports = {
plugins: [
require('@tailwindcss/forms'), // 重設並美化表單元素
require('@tailwindcss/typography'), // prose class:自動排版文章內容
require('@tailwindcss/aspect-ratio'), // 比例控制
require('@tailwindcss/container-queries'), // 容器響應式
// 自訂插件①:新增 text-shadow utilities(帶 theme 整合)
plugin(function({ addUtilities, theme }) {
addUtilities({
'.text-shadow-sm': { textShadow: '0 1px 2px rgba(0,0,0,0.3)' },
'.text-shadow': { textShadow: '0 2px 4px rgba(0,0,0,0.3)' },
'.text-shadow-lg': { textShadow: '0 4px 8px rgba(0,0,0,0.4)' },
'.text-shadow-none': { textShadow: 'none' },
})
}),
// 自訂插件②:scrollbar 控制(含跨瀏覽器支援)
plugin(function({ addUtilities }) {
addUtilities({
'.scrollbar-hide': {
'-ms-overflow-style': 'none',
'scrollbar-width': 'none',
'&::-webkit-scrollbar': { display: 'none' },
},
'.scrollbar-thin': {
'scrollbar-width': 'thin',
'&::-webkit-scrollbar': { width: '6px', height: '6px' },
'&::-webkit-scrollbar-track': { background: 'transparent' },
'&::-webkit-scrollbar-thumb': { borderRadius: '3px' },
},
})
}),
// 自訂插件③:新增自訂 variant(依父元素狀態)
plugin(function({ addVariant }) {
addVariant('sidebar-open', '[data-sidebar-open] &')
addVariant('htmx-settling', '&.htmx-settling')
addVariant('peer-checked-sibling', '.peer:checked ~ &')
}),
// 自訂插件④:帶有 theme 值的動態 utilities
plugin(function({ addUtilities, theme }) {
const colors = theme('colors')
const gradients = {}
Object.entries(colors).forEach(([name, value]) => {
if (typeof value === 'string') {
gradients[`.gradient-to-${name}`] = {
backgroundImage: `linear-gradient(to right, transparent, ${value})`,
}
}
})
addUtilities(gradients)
}),
// 自訂插件⑤:matchUtilities 動態值(支援任意值語法)
plugin(function({ matchUtilities, theme }) {
matchUtilities(
{ 'tab': (value) => ({ tabSize: value }) },
{ values: theme('tabSize', { 2: '2', 4: '4', 8: '8' }) }
)
}),
],
}
matchUtilities 定義的 utility 自動支援 [] 任意值語法(如 tab-[6]),並與 theme() 函式整合,讓插件的使用體驗與內建 utilities 完全一致。addUtilities 則適合固定的靜態 class 集合。兩者均支援 supportsNegativeValues: true 選項,自動生成負值版本(如 -tab-4)。
- @tailwindcss/forms:重設並統一 input、select、textarea 跨瀏覽器外觀,支援 base(全局套用)與 class(按需套用
form-input)兩種策略 - @tailwindcss/typography:prose class 自動為 Markdown 或富文字建立精美排版,支援深淺色、尺寸變體(prose-sm、prose-lg)
- @tailwindcss/aspect-ratio:控制元素的固定寬高比例,適合嵌入影片、地圖或響應式圖片
- @tailwindcss/container-queries:基於容器寬度(而非視窗)的響應式 class,父層加 @container,子元素用 @sm:、@lg: 等前綴
設計系統 Token 管理(theme.extend 最佳實踐)
企業級專案需要一套完整的設計 Token 系統,讓品牌色彩、字型、間距等規格集中管理。透過 tailwind.config.js 的 theme.extend,可以建立與品牌設計語言完全吻合的 Token 體系,同時不破壞 Tailwind 的預設 utility class。設計 Token 的核心原則:一處定義,全域使用,改一個地方,全站生效。良好的 Token 架構應分為三層——原始 Token(品牌原色)、語意化 Token(介面角色)、元件 Token(元件專屬)。
// tailwind.config.js — 完整三層設計系統 Token 範例
module.exports = {
theme: {
extend: {
// 第一層:原始品牌色階(Primitive Tokens)
colors: {
brand: {
50: '#eff6ff',
100: '#dbeafe',
300: '#93c5fd',
500: '#4a9eda', // 主要品牌色
700: '#1d6fa4',
900: '#1e3a5f',
},
// 第二層:語意化介面色(Semantic Tokens)
surface: '#0f172a',
'surface-raised': '#1e293b',
'surface-overlay': '#2d3f55',
'on-surface': '#e2e8f0',
'on-surface-muted': '#94a3b8',
'on-surface-subtle': '#475569',
// 狀態色
'status-success': '#22c55e',
'status-error': '#ef4444',
'status-warning': '#f59e0b',
'status-info': '#38bdf8',
},
// 品牌字型堆疊(對應設計稿的 Typography Scale)
fontFamily: {
sans: ['Noto Sans TC', 'Inter', 'system-ui', 'sans-serif'],
mono: ['JetBrains Mono', 'Fira Code', 'Consolas', 'monospace'],
display: ['Cal Sans', 'Inter', 'sans-serif'],
},
// 設計稿規格間距(對應 8pt grid 系統)
spacing: {
'4.5': '1.125rem', // 18px — 8pt grid 補充值
'18': '4.5rem', // 72px
'22': '5.5rem', // 88px
'112': '28rem', // 448px — 常用 panel 寬度
'128': '32rem', // 512px
'144': '36rem', // 576px
},
// 自訂動畫 Token(與 Motion Design 系統對應)
keyframes: {
fadeIn: { from: { opacity: '0' }, to: { opacity: '1' } },
slideUp: { from: { transform: 'translateY(12px)', opacity: '0' },
to: { transform: 'translateY(0)', opacity: '1' } },
scaleIn: { from: { transform: 'scale(0.95)', opacity: '0' },
to: { transform: 'scale(1)', opacity: '1' } },
},
animation: {
'fade-in': 'fadeIn 0.3s ease-in-out',
'slide-up': 'slideUp 0.4s ease-out',
'scale-in': 'scaleIn 0.25s ease-out',
'pulse-slow': 'pulse 3s ease-in-out infinite',
},
// 自訂 z-index 層級(避免魔法數字)
zIndex: {
'nav': '100', 'overlay': '200', 'modal': '300', 'toast': '400',
},
// 自訂陰影(品牌光暈 + 深色模式陰影)
boxShadow: {
brand: '0 4px 24px rgba(74, 158, 218, 0.25)',
'brand-lg': '0 8px 40px rgba(74, 158, 218, 0.35)',
dark: '0 4px 20px rgba(0,0,0,0.5)',
},
},
},
}
surface、on-surface)與品牌原始色(如 brand-500)分層定義。語意化顏色可進一步對應 CSS 自訂屬性(var(--color-surface)),這樣實作 Dark Mode 時只需更新 CSS 變數的值,而不需要在每個元素上堆疊 dark: 前綴,大幅降低維護成本,也讓主題切換動畫更流暢。建議搭配 tailwind-merge 或 cva(class-variance-authority)管理帶有 variant 的元件 Token。
theme.extend vs 完整覆蓋:永遠使用 theme.extend 新增 Token,除非你明確知道自己要完全替換某個類別。直接在 theme 下覆蓋 colors、spacing 或 fontFamily,會移除對應的所有 Tailwind 預設 utility class(如 text-blue-500、p-4、font-sans 全部消失),且錯誤訊息不明顯,排查費時。只有建立完全隔離的品牌設計系統時,才考慮完整覆蓋。
Dark Mode 進階(class 策略配合切換 JS)
Tailwind 支援三種 Dark Mode 策略。進階用法可讓系統偏好與手動切換並存,並將用戶選擇記憶在 localStorage,在頁面重載後保持主題一致,同時對首次訪問的用戶自動跟隨作業系統設定:
- media(預設)——跟隨系統的暗色偏好(
prefers-color-scheme: dark),純 CSS,無法手動覆蓋,適合不需要主題切換鈕的靜態網站 - class(推薦)——手動控制,在
<html>加上class="dark"即可切換,可搭配 JS 實作切換鈕與記憶功能,是最靈活的生產環境策略 - selector(v3.4+)——使用任意 CSS 選擇器觸發暗色模式,適合多主題系統(如
[data-theme="dark"]),或需要支援系統偏好與手動覆蓋雙重邏輯的複雜場景
// tailwind.config.js — 啟用 class 策略
module.exports = {
darkMode: 'class', // 推薦:完全手動控制
// Tailwind v3.4+ 支援自訂選擇器(多主題場景)
// darkMode: ['variant', '&:is([data-theme="dark"] *)'],
// 同時支援系統偏好 + 手動類別雙重邏輯:
// darkMode: ['variant', [
// '@media (prefers-color-scheme: dark) { &:not(.light *) }',
// '&:is(.dark *)',
// ]],
}
<!-- 使用 dark: 前綴設定暗色樣式,搭配 transition 讓切換柔和 -->
<div class="bg-white dark:bg-gray-900 transition-colors duration-300">
<h1 class="text-gray-900 dark:text-white">標題</h1>
<p class="text-gray-600 dark:text-gray-300">內文段落</p>
<!-- 組合 hover + dark -->
<button class="bg-blue-500 dark:bg-blue-400
hover:bg-blue-600 dark:hover:bg-blue-300
text-white rounded-lg px-4 py-2
transition-colors duration-150"
>按鈕</button>
<!-- 卡片深淺色 -->
<div class="bg-gray-50 dark:bg-gray-800
border border-gray-200 dark:border-gray-700
rounded-xl p-6 shadow-sm dark:shadow-dark">
<span class="text-gray-500 dark:text-gray-400 text-sm">卡片副標</span>
</div>
</div>
<!-- 手動切換 + 記憶用戶選擇(建議放在 <head> 末尾,防止 FOUC)-->
<script>
// 立即執行:在首次繪製前套用主題,避免主題閃爍
;(function() {
const saved = localStorage.getItem('theme')
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
if (saved === 'dark' || (!saved && prefersDark)) {
document.documentElement.classList.add('dark')
}
})()
</script>
<!-- 切換按鈕(可放在 <body> 任何位置)-->
<script>
function toggleTheme() {
const isDark = document.documentElement.classList.toggle('dark')
localStorage.setItem('theme', isDark ? 'dark' : 'light')
// 同步更新切換鈕圖示
document.getElementById('theme-icon').textContent = isDark ? '☀' : '☾'
}
// 監聽系統主題變化(當用戶未手動設定時跟隨系統)
window.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', e => {
if (!localStorage.getItem('theme')) {
document.documentElement.classList.toggle('dark', e.matches)
}
})
</script>
<head> 最末尾,且不加 defer 或 type="module",確保在瀏覽器首次繪製前就已套用 dark class。若使用 Next.js,改用 next-themes;Nuxt 可用 @nuxtjs/color-mode,它們均已內建防閃爍機制,開箱即用。
深色模式的圖片處理:可搭配 dark:brightness-75 或 dark:opacity-80 讓圖片在深色背景下不過於刺眼。SVG 圖示則建議使用 currentColor 填色,配合 text-gray-900 dark:text-white 即可自動切換顏色,無需另外撰寫深色版本。
響應式設計進階(container / screens 自訂)
Tailwind 採用行動優先(Mobile First)策略:未加斷點前綴的 class 適用於所有螢幕尺寸,斷點前綴代表「此尺寸以上才套用」。v3.2+ 新增的 max-* 前綴支援「此尺寸以下才套用」的反向設計,讓範圍限定變得更直觀,消除了過去需要在較大斷點設定 hidden 再在較小斷點設定 block 的冗餘寫法。
<!-- 預設斷點(可在 config 自訂覆蓋)--> sm: 640px 以上 md: 768px 以上 lg: 1024px 以上 xl: 1280px 以上 2xl: 1536px 以上 <!-- 行動優先的卡片格線佈局 --> <div class=" grid grid-cols-1 <!-- 手機:單欄 --> sm:grid-cols-2 <!-- 小平板以上:雙欄 --> lg:grid-cols-3 <!-- 桌機以上:三欄 --> gap-4 lg:gap-6 "> <!-- 文字響應式縮放 --> <h1 class="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold"> <!-- 範圍限定(v3.2+ max-* 前綴)--> <nav class="max-lg:hidden">桌機側欄導覽(lg 以上顯示)</nav> <button class="lg:hidden">行動版選單鈕(lg 以下顯示)</button> <aside class="sm:max-lg:w-48">只在 sm~lg 之間設為固定寬度</aside> <!-- 任意值語法指定精確媒體查詢 --> <div class="[@media(min-width:1100px)]:grid-cols-4"> <!-- 自動填充欄數(無需手動設定斷點)--> <div class="grid-cols-[repeat(auto-fill,minmax(280px,1fr))]">
// 自訂斷點與 container(tailwind.config.js)
module.exports = {
theme: {
// 覆蓋斷點(注意:這會取代所有預設斷點,需完整列出)
screens: {
xs: '480px', // 新增超小螢幕斷點
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px',
'3xl': '1920px', // 超大螢幕(4K 顯示器)
// 可改用 em 單位(無障礙友好,跟隨用戶字型縮放)
// sm: '40em', md: '48em', lg: '64em',
},
extend: {
container: {
center: true, // 預設水平置中
padding: {
DEFAULT: '1rem',
sm: '1.5rem',
lg: '2rem',
xl: '3rem',
},
screens: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1200px', // 自訂 xl 的 container 最大寬度
'2xl': '1400px',
},
},
},
},
}
grid-cols-[repeat(auto-fill,minmax(280px,1fr))] 任意值語法,讓格線根據容器寬度自動計算欄數,不再依賴手動設定斷點。搭配 @tailwindcss/container-queries 插件,可進一步基於父容器寬度而非視窗寬度做響應式設計,讓元件更具可攜性,在側欄、Modal、CMS 區塊等不同寬度的容器中均能正確響應。
效能最佳化(Bundle Size 與 Content Purge 設定)
Tailwind v3 的 JIT 引擎已大幅改善輸出體積問題,但在大型專案中仍有幾個最佳化要點需要留意。生產環境的 CSS bundle 大小直接影響首頁載入速度與 Core Web Vitals 分數,特別是 FCP(First Contentful Paint)對 render-blocking CSS 非常敏感。
- 正確設定 content 路徑:確保掃描範圍涵蓋所有使用 class 的檔案,包含 JS 工具函式、設定物件,但排除 node_modules、建置產物等無關目錄,過度寬泛的 glob 會拖慢編譯速度
- 謹慎使用 safelist:每個 safelist 條目都強制保留在輸出中,應盡量使用精確的 pattern 格式並限制 variants 範圍,避免意外保留大量 class,讓 bundle 不受控膨脹
- 生產環境自動 Tree-shake:Tailwind 在
NODE_ENV=production時自動移除未使用的 class,無需額外設定,與 Vite、webpack、esbuild 的 tree-shaking 機制協同運作 - 搭配 CSS 壓縮工具:使用 cssnano 或 lightningcss 壓縮輸出,可進一步縮小 15–30% 的 bundle 體積,
lightningcss同時提供自動 vendor prefix 補全 - 避免掃描大型 JSON 或資料檔:content 設定中意外掃描到含有大量字串的 JSON 資料,可能讓 safelist 膨脹並拖慢掃描速度
- Critical CSS 分割:對於 SSR 應用,可考慮將首屏所需的 utility class 提取為 Critical CSS 內聯在
<head>中,其餘非同步載入,大幅提升 FCP 分數
# postcss.config.js — 生產環境最佳化設定
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
// 僅在生產環境啟用 CSS 壓縮(開發環境不壓縮以保留可讀性)
...(process.env.NODE_ENV === 'production'
? { cssnano: { preset: ['default', {
discardComments: { removeAll: true },
mergeLonghand: true,
colormin: true,
}] } }
: {}),
},
}
# 典型生產環境 CSS 輸出體積(gzip 後)參考
# 小型網站(~20 個頁面,無 safelist): 5–10 KB
# 中型應用(~100 個元件): 15–25 KB
# 大型應用(含 safelist pattern): 30–50 KB
# 超大應用(完整設計系統 token): 40–70 KB
# 對比:Bootstrap v5 未壓縮 ~200 KB,Material UI CSS ~300 KB
# Tailwind 在實際專案中幾乎永遠更小,因為只輸出使用到的 class
const statusColor = { error: 'bg-red-500 text-red-700', success: 'bg-green-500 text-green-700' }。這個物件出現在 content 路徑掃描範圍內,JIT 就能識別這些完整字串並自動輸出,完全不需要 safelist。此方式同時讓 class 的對應關係集中管理,易於維護與重構。
purge 陣列,使用寬鬆正規表達式掃描,偶有誤刪 class 或漏掉動態 class 的問題,且開發環境 CSS 體積高達數 MB。v3 整合為 content 設定,採用更精確的字串掃描演算法,開發與生產環境均使用同一套機制(僅壓縮程度不同),誤刪狀況大幅減少。遇到 class 被意外移除時,首先確認 content 路徑是否涵蓋該檔案,其次檢查是否有動態字串拼接問題。