Skip to content

TailwindCSS:utility-first 不只是風格偏好,是一種 CSS 管理策略

2026年3月27日 1 分鐘
TL;DR TailwindCSS 解決的核心問題是 CSS 的全域命名汙染和死碼問題。utility class 讓樣式跟元件放在一起,build 時自動移除沒用到的 class,生產環境的 CSS bundle 通常只有幾十 KB。島島和 NobodyClimb 都用它做 web 端樣式。
目錄
  1. 核心概念
  2. 實際的使用模式
  3. 設定客製化
  4. Tailwind v4 的變化
  5. 需要注意的地方
  6. 整體來說
  7. 參考資料

🌏 English version

CSS 有一個根本問題:它的作用域是全域的。你寫 .button { color: blue } ,這個規則影響所有帶有 button class 的元素。解法出現過很多:BEM 命名規範、CSS Modules、CSS-in-JS——每一種都在嘗試解決「怎麼讓 CSS 的範圍可控」這個問題。

TailwindCSS 選擇了另一條路:不用語義化 class,直接用描述視覺效果的 utility class。樣式和元件放在一起,沒有命名問題,也不用擔心改一個 class 影響其他地方。

島島(DaoDao)的 packages/ui 和兩個 Next.js app,以及 NobodyClimb 的 Next.js web 端,都用 TailwindCSS 做樣式。

核心概念

Utility-first 的意思是:與其寫 .card { padding: 16px; border-radius: 8px; background: white },你直接在 HTML/JSX 裡用已有的 utility class:

// 傳統 CSS 方式
<div className="card">
  <h2 className="card-title">標題</h2>
</div>

// Tailwind 方式
<div className="p-4 rounded-lg bg-white shadow-sm border border-gray-200">
  <h2 className="text-xl font-bold text-gray-900">標題</h2>
</div>

第一眼看 Tailwind 的寫法,很多人的反應是「這跟 inline style 有什麼差」。差別在幾個地方:

設計系統約束:Tailwind 的 spacing scale(p-1 = 4px, p-2 = 8px, p-4 = 16px...)、color palette、font size 是預先定義好的,你只能從這個系統裡選,不容易寫出 padding: 13px 這種破壞一致性的值。

Responsive 和狀態md:flexhover:bg-blue-600dark:text-white 這類修飾符直接加在 class 上,不需要寫 media query 或 pseudo-class:

<button className="
  bg-blue-500 text-white px-4 py-2 rounded
  hover:bg-blue-600
  focus:outline-none focus:ring-2 focus:ring-blue-500
  dark:bg-blue-400
  md:px-6
">
  送出
</button>

Build-time purge:Tailwind v3+ 預設只輸出你實際用到的 class,生產環境的 CSS 通常只有 5-20 KB,不管你的 Tailwind 設定有多少 token。

實際的使用模式

在 React 元件裡,重複的 class 組合用變數或函式抽出:

import { cn } from "@/lib/utils"; // clsx + tailwind-merge 的包裝

// 基本用法
function Card({ className, children }: CardProps) {
  return (
    <div className={cn("rounded-lg border bg-white p-4 shadow-sm", className)}>
      {children}
    </div>
  );
}

// 有 variant 的元件
function Badge({ variant = "default", children }: BadgeProps) {
  const variantClasses = {
    default: "bg-blue-100 text-blue-800",
    success: "bg-green-100 text-green-800",
    warning: "bg-yellow-100 text-yellow-800",
    danger: "bg-red-100 text-red-800",
  };

  return (
    <span className={cn("rounded-full px-2 py-1 text-xs font-medium", variantClasses[variant])}>
      {children}
    </span>
  );
}

cn 函式(通常是 clsx + tailwind-merge 組合)解決兩個問題:一是條件式 class 的組合,二是 class 衝突(例如 p-4p-2 同時存在時,merge 確保後者勝出):

import { clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

設定客製化

tailwind.config.js(或 Tailwind v4 的 CSS config)讓你擴充或覆蓋設計系統:

// tailwind.config.js
module.exports = {
  content: ["./src/**/*.{ts,tsx}"],
  theme: {
    extend: {
      colors: {
        brand: {
          50: "#eff6ff",
          500: "#3b82f6",
          900: "#1e3a8a",
        },
      },
      fontFamily: {
        sans: ["Inter", "sans-serif"],
      },
      borderRadius: {
        xl: "12px",
        "2xl": "16px",
      },
    },
  },
};

擴充後就可以用 text-brand-500font-sansrounded-2xl 這些自訂 token。

Tailwind v4 的變化

Tailwind v4(2025 年發布)把設定從 JS 移到 CSS:

/* app.css */
@import "tailwindcss";

@theme {
  --color-brand: #3b82f6;
  --font-sans: "Inter", sans-serif;
}

不再需要 tailwind.config.js,設定放在 CSS 檔案裡,更接近 CSS 原生的設計理念。shadcn/ui 的最新版本也跟著遷移到 v4。

需要注意的地方

Class 順序問題hover:bg-blue-500 bg-blue-600bg-blue-600 hover:bg-blue-500 在 Tailwind 的輸出裡結果一樣,但如果你同時有 p-4p-2 在不同條件下,不用 tailwind-merge 的話,哪個生效取決於 CSS 的 class 順序,而不是 JSX 裡的順序。

Linter 的 class 排序:class 一多,順序各自為政會讓程式碼難讀。prettier-plugin-tailwindcss 可以自動排序,讓同樣語義的 class 總是在同一個位置。

語義化 class 缺失:Tailwind 的 class 名稱是視覺描述(text-blue-500),不是語義(text-primary)。如果設計系統改了主色,你需要找遍所有 text-blue-500 替換,除非你用 CSS variables 包一層(shadcn/ui 的做法)。

HTML 的可讀性:元件帶十幾個 class 是常態,對初次看程式碼的人不友善。解法是把相關的 class 抽成元件,而不是把所有樣式堆在同一個 JSX 元素上。

整體來說

TailwindCSS 的爭議通常集中在「class 太多、太醜」這個點,但這其實是次要問題。主要問題是:CSS 的全域命名和死碼,Tailwind 都解了。

在 React 生態裡,Tailwind 和元件化思維是天生搭配——每個元件管自己的 class,沒有全域衝突,build 時自動 tree-shake,設計系統的約束讓視覺一致性可控。

如果你在一個 React 專案裡還在用傳統 CSS class,值得認真考慮遷移。

參考資料