React + Tailwind CSS 框架
Tailwind CSS 是一個 Utility-First 的 CSS 框架。與傳統 CSS 框架(如 Bootstrap)提供現成的元件不同,Tailwind 提供的是底層的 utility classes(如 flex, pt-4, text-center, rotate-90),讓你可以直接在 HTML/JSX 中快速構建任何設計。
核心概念 (Utility-First)
傳統的 CSS (如 Bootstrap) 給你完整的元件 (Components),例如 btn, card。你需要覆蓋樣式時往往需要寫更重的 CSS selector。
Tailwind 給你的是 Utility Classes (工具類別)。每個 class 通常只做一件事,例如 text-center 就是 text-align: center。
為什麼這樣比較好?
- 不再為 Class 命名煩惱:不需要想
sidebar-inner-wrapper這種名字。 - CSS 檔案停止膨脹:既然都是重用 utility,你的 CSS 檔案大小通常在起步後就會維持穩定。
- 更安全的改動:改 HTML 的 class 只會影響該元素,不會像改全域 CSS 一樣意外弄壞其他頁面。
安裝與設定
在 Vite 專案中安裝 Tailwind CSS 是最常見的情境。
安裝套件
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
這會產生 tailwind.config.js 和 postcss.config.js 兩個設定檔。
設定 Template Paths
修改 tailwind.config.js,告訴 Tailwind 你的檔案在哪裡,這樣它才能掃描你有用到的 class 並生成對應的 CSS(Tree Shaking)。
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
};
加入 Tailwind Directives
在你的全域 CSS 檔案(例如 index.css)的最上方加入:
@tailwind base;
@tailwind components;
@tailwind utilities;
基本用法
Utility Classes
直接在 className 中寫 utility class:
function Card() {
return (
<div className="max-w-sm rounded overflow-hidden shadow-lg bg-white">
<div className="px-6 py-4">
<div className="font-bold text-xl mb-2">Tailwind Card</div>
<p className="text-gray-700 text-base">
這是一個使用 Tailwind CSS 樣式的簡單卡片元件。 所有的樣式都是透過 utility classes 定義的。
</p>
</div>
</div>
);
}
響應式設計 (Responsive Design)
Tailwind 採用 Mobile-First (行動裝置優先) 的設計策略。這意味著:
- 不加前綴的 utility (如
w-full) 對所有螢幕生效(通常代表手機) md:前綴代表「在中型螢幕以上」才覆蓋原本樣式。
常用的斷點:
sm: 640pxmd: 768pxlg: 1024pxxl: 1280px2xl: 1536px
範例:手機版垂直排列,電腦版並排
<div className="flex flex-col md:flex-row">
<div className="w-full md:w-1/2 bg-blue-200">左側</div>
<div className="w-full md:w-1/2 bg-blue-400">右側</div>
</div>
偽類與狀態 (States)
除了常見的 hover:, focus:, active:,Tailwind 還提供了更進階的狀態選擇器:
Group Hover
當你想要 hover 父元素時,改變子元素的樣式,可以使用 group 和 group-hover。
<div className="group border p-4 hover:bg-blue-500 cursor-pointer">
<h3 className="text-black group-hover:text-white">標題</h3>
<p className="text-gray-500 group-hover:text-blue-100">說明文字...</p>
</div>
Peer
根據兄弟元素 (Sibling) 的狀態來改變樣式(例如:Input 有錯誤時,顯示下方的錯誤訊息)。
<form>
<input type="email" className="peer border border-gray-300 p-2" />
{/* 只有當 peer (input) 是 invalid 時,才顯示這行與 pink 文字 */}
<p className="mt-2 invisible peer-invalid:visible text-pink-600">Email 格式錯誤</p>
</form>
暗黑模式 (Dark Mode)
Tailwind 內建深色模式支援。只需在 class 前加上 dark: 前綴。
<div className="bg-white dark:bg-slate-800 rounded-lg px-6 py-8 ring-1 ring-slate-900/5 shadow-xl">
<h3 className="text-slate-900 dark:text-white mt-5 text-base font-medium tracking-tight">
Writes Upside-Down
</h3>
<p className="text-slate-500 dark:text-slate-400 mt-2 text-sm">
The Zero Gravity Pen can be used to write in any orientation, including space.
</p>
</div>
設定切換方式:
在 tailwind.config.js 中設定 darkMode: 'class',這樣你就可以透過在 <html> 標籤加上 class="dark" 來手動切換模式。
動態 Class 處理與衝突解決
在 React + Tailwind 開發中,處理 ClassName 時常會遇到兩個挑戰:
1. 如何條件式地加入 Class?
你可能寫過這樣的程式碼:
<div className={`p-4 rounded ${isActive ? 'bg-blue-500' : 'bg-gray-200'}`}>
當條件變多時,字串串接會變得很醜。這時我們推薦使用 clsx (或 classnames) 套件。
import clsx from 'clsx';
// 結果: "p-4 rounded bg-blue-500" (若 isActive 為 true)
clsx('p-4 rounded', isActive && 'bg-blue-500', !isActive && 'bg-gray-200');
2. Class 衝突 (CSS 權重問題)
假設你設計了一個 <Button> 元件,預設有 p-4 (padding: 1rem) 的樣式:
function Button({ className }) {
return <button className={`p-4 bg-blue-500 ${className}`}>按鈕</button>;
}
當使用者傳入 p-8 想覆蓋它時:<Button className="p-8" />
最終 HTML 會是:class="p-4 bg-blue-500 p-8"。
但在 CSS 中,同屬性的樣式誰贏,是取決於 CSS 檔案中定義的順序,而不是 class 出現的順序!
而在 Tailwind 的 CSS 定義裡,p-4 和 p-8 的順序是固定的,所以有可能你傳了 p-8 卻還是顯示 p-4 的效果。
最佳解決方案: tailwind-merge
tailwind-merge 這個套件能理解 Tailwind 的 class 邏輯,它知道 p-8 和 p-4 是衝突的,並且後者應該覆蓋前者。它會算出最終結果只剩下 bg-blue-500 p-8。
實作 cn Utility
在專案中,我們通常會結合 clsx 和 tailwind-merge 封裝成一個通用的 cn (classname) 函式:
npm install clsx tailwind-merge
// lib/utils.js 或 utils/cn.js
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs) {
return twMerge(clsx(inputs));
}
使用範例:
import { cn } from './utils/cn';
function Button({ className, variant, ...props }) {
return (
<button
className={cn(
'py-2 px-4 rounded font-semibold', // 基礎樣式
'bg-blue-500 hover:bg-blue-600', // 預設樣式
className, // 外部傳入的樣式,可以安全地覆蓋前面 (例如 p-8 會贏過 py-2 px-4)
)}
{...props}
/>
);
}
樣式重用 (Reusing Styles)
雖然我們鼓勵直接寫 utility,但在某些情況下(例如多個按鈕長得一樣),你有兩種重用策略:
1. 抽取成 React Component (推薦)
這是最符合 React 哲學的做法。
function PrimaryButton({ children }) {
return (
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
{children}
</button>
);
}
2. 使用 @apply (CSS 方式)
如果你需要覆蓋第三方套件的樣式,或者某些樣式真的太長太複雜,可以使用 @apply 將其抽取到 CSS 檔案中。
/* index.css */
@layer components {
.btn-primary {
@apply bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded;
}
}
客製化設定 (Configuration)
你可以在 tailwind.config.js 中擴充或修改預設的主題 (Theme)。
/** @type {import('tailwindcss').Config} */
export default {
theme: {
extend: {
colors: {
// 自定義顏色: 使用 text-brand 或 bg-brand
brand: '#1DA1F2',
},
spacing: {
// 自定義間距: 使用 p-128
128: '32rem',
},
},
},
};
總結
Tailwind CSS 提供了一種極快、可維護且一致的方式來撰寫樣式。搭配 React 元件化開發,你可以將複雜的 utility classes 封裝在元件內部,同時享受原子化 CSS 帶來的極小 CSS 檔案體積優勢。