進階技巧

任意值語法、JIT 原理、@apply 陷阱、插件開發、設計 Token、Dark Mode 切換、響應式自訂、效能調校——全面掌握 Tailwind CSS 的高階用法。

任意值語法(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))]">
使用時機建議:任意值最適合一次性的特殊數值(如設計稿精確像素規格或第三方 API 回傳的動態樣式)。若同一個任意值在專案中出現超過 3 次,應考慮將其加入 tailwind.config.jstheme.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 的完整組合。理解其工作原理,是避開常見陷阱的關鍵所在。

// 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',
  ],
  // ...
}
最常見踩雷:動態拼接 class 名稱。`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; }
}
不適合用 @apply 的情境:
  • 只出現一兩次的樣式——直接在 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 的特異性陷阱:透過 @apply 合併的 class,其最終 CSS 特異性取決於 @layer 位置,而非原始 utility 的特異性。將 @apply 放在 @layer components 內確保它在 utilities 之前,讓個別 utility class 仍可在 HTML 端覆蓋元件樣式。若直接在 @layer 外使用 @apply,產生的 CSS 特異性可能意外高於 utilities,導致難以覆蓋的樣式問題。

自訂插件開發基本範例

Tailwind 的插件系統允許你以 JavaScript 函式擴充框架,新增自訂 utilities、components 或 variants,並可將其打包為 npm 套件發布共享。插件 API 提供 addUtilitiesaddComponentsaddBaseaddVariant 四個核心方法,覆蓋幾乎所有自訂需求。

# 安裝官方插件
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 vs addUtilities:使用 matchUtilities 定義的 utility 自動支援 [] 任意值語法(如 tab-[6]),並與 theme() 函式整合,讓插件的使用體驗與內建 utilities 完全一致。addUtilities 則適合固定的靜態 class 集合。兩者均支援 supportsNegativeValues: true 選項,自動生成負值版本(如 -tab-4)。

設計系統 Token 管理(theme.extend 最佳實踐)

企業級專案需要一套完整的設計 Token 系統,讓品牌色彩、字型、間距等規格集中管理。透過 tailwind.config.jstheme.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)',
      },
    },
  },
}
設計 Token 進階最佳實踐:將語意化顏色(如 surfaceon-surface)與品牌原始色(如 brand-500)分層定義。語意化顏色可進一步對應 CSS 自訂屬性(var(--color-surface)),這樣實作 Dark Mode 時只需更新 CSS 變數的值,而不需要在每個元素上堆疊 dark: 前綴,大幅降低維護成本,也讓主題切換動畫更流暢。建議搭配 tailwind-mergecva(class-variance-authority)管理帶有 variant 的元件 Token。

theme.extend vs 完整覆蓋:永遠使用 theme.extend 新增 Token,除非你明確知道自己要完全替換某個類別。直接在 theme 下覆蓋 colorsspacingfontFamily,會移除對應的所有 Tailwind 預設 utility class(如 text-blue-500p-4font-sans 全部消失),且錯誤訊息不明顯,排查費時。只有建立完全隔離的品牌設計系統時,才考慮完整覆蓋。

Dark Mode 進階(class 策略配合切換 JS)

Tailwind 支援三種 Dark Mode 策略。進階用法可讓系統偏好與手動切換並存,並將用戶選擇記憶在 localStorage,在頁面重載後保持主題一致,同時對首次訪問的用戶自動跟隨作業系統設定:

// 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>
避免 FOUC(Flash of Unstyled Content,主題閃爍):將主題初始化的立即執行函式(IIFE)放在 <head> 最末尾,且不加 defertype="module",確保在瀏覽器首次繪製前就已套用 dark class。若使用 Next.js,改用 next-themes;Nuxt 可用 @nuxtjs/color-mode,它們均已內建防閃爍機制,開箱即用。

深色模式的圖片處理:可搭配 dark:brightness-75dark: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 非常敏感。

# 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
Safelist 最佳替代方案:與其大量使用 safelist,更好的方式是在 JavaScript 中建立完整 class 名稱的對照物件,例如 const statusColor = { error: 'bg-red-500 text-red-700', success: 'bg-green-500 text-green-700' }。這個物件出現在 content 路徑掃描範圍內,JIT 就能識別這些完整字串並自動輸出,完全不需要 safelist。此方式同時讓 class 的對應關係集中管理,易於維護與重構。
v3 JIT vs 舊版 PurgeCSS 的關鍵差異:舊版 Tailwind(v1/v2)需手動設定 purge 陣列,使用寬鬆正規表達式掃描,偶有誤刪 class 或漏掉動態 class 的問題,且開發環境 CSS 體積高達數 MB。v3 整合為 content 設定,採用更精確的字串掃描演算法,開發與生產環境均使用同一套機制(僅壓縮程度不同),誤刪狀況大幅減少。遇到 class 被意外移除時,首先確認 content 路徑是否涵蓋該檔案,其次檢查是否有動態字串拼接問題。