FE Day 8 樣式方案:Tailwind 與設計慣例
執行需求:CPU 可跑。今天是「前端開發實戰:React 與 Next.js 全套」系列的第八篇。Day 5 到 Day 7 我們把元件、state、渲染模型都學會了,但寫出來的畫面還是「工程師風格」的——預設顏色、預設字級、沒有設計感。今天要解決這個問題:認識前端主流樣式方案、理解為什麼選 Tailwind CSS、學會 Tailwind 4 的 CSS-first 設定、掌握幾個常見的設計慣例(design token、間距尺度、字體階梯、響應式)。讀完之後,你應該能把前幾天寫的元件套上 Tailwind、寫出有設計感的畫面,並能用設計系統的角度思考「這份 UI 的風格怎麼統一」。整篇閱讀時間約三十五分鐘,動手做約二十五分鐘。
引言
後端工程師第一次碰前端樣式,常會卡在「要選什麼方案」。CSS Modules、SASS、styled-components、Emotion、Stitches、Tailwind——每個都有人推薦,每個都有擁護者。事實上沒有一個方案「最好」,只有「最適合這個專案」。本系列選擇 Tailwind,是因為它在 2026 年的 React 生態裡是最主流的選擇(Vercel、Next.js、shadcn/ui、Tremor 等社群套件都內建支援),而且它跟 React 元件設計的理念非常契合:把樣式視為元件的「組成部分」,而不是分開的 CSS 檔。
今天要解開五個疑問:第一,前端樣式方案的分類與取捨;第二,Tailwind 4 在 2025 年初發佈後有什麼新設計;第三,CSS-first 設定怎麼寫(取代 v3 的 tailwind.config.js);第四,utility class 怎麼跟 React 元件搭配(特別是「有條件的 className」);第五,設計系統與 design token 的概念,怎麼把 Tailwind 的設定檔變成團隊的「視覺字典」。最後用一個完整的「預約卡片」範例把前述觀念串起來,並展示 Tailwind 4 的自訂主題怎麼用。
前端樣式方案的分類與取捨
前端樣式方案大致可以分成五大類,各有不同的設計哲學:
- 全域 CSS:寫在
.css檔、用 class selector 套用。最傳統但難以模組化,class 名稱容易衝突。適合非常小的專案或純行銷頁。 - CSS Modules:把 CSS 檔綁定到特定元件(
Button.module.css),class 名稱會被編譯器改寫成唯一值,避免衝突。適合喜歡「CSS 跟 JS 分開」的團隊。 - CSS-in-JS:styled-components、Emotion 等,把樣式寫在 JS 檔裡。動態樣式能力強,但有 runtime overhead(執行時產生 CSS 字串)。React 19 之後這個類別的使用率下降。
- Utility-first:Tailwind 主導,提供大量短小的 utility class(
bg-blue-500、p-4、flex),直接在 HTML/JSX 上組合。HTML 看起來很長、但元件檔案就是「樣式 + 結構」一站搞定。 - 元件庫 + 主題:shadcn/ui、Mantine、Chakra 等,預先寫好元件、按主題切換外觀。適合需要快速打造 MVP 的團隊。
2026 年的主流選擇是「Tailwind + 輕量元件庫」。Vercel 的官方樣板(next-app-tailwind)預裝 Tailwind 4,shadcn/ui 把 Tailwind 推到主流,Vite 的 React 樣板也內建。Tailwind 4 在 2025 年初發佈後,做了兩個關鍵改變:CSS-first 設定(不再需要 tailwind.config.js)與更快的編譯速度(用 Rust 寫的引擎)。本系列從 Day 8 之後所有元件都會用 Tailwind 4。
Tailwind 4 的 CSS-first 設定
Tailwind 4 最顯眼的改變是「設定檔從 JavaScript 搬到 CSS」。v3 時代你會寫 tailwind.config.js 設定主題、擴充顏色、調整斷點;v4 把這些都搬到 CSS 檔裡,用 @theme 區塊宣告 design tokens。
/* src/styles/app.css */
/* Tailwind 4 的 CSS-first 設定 */
@import "tailwindcss";
/* 用 @theme 定義設計 token:顏色、字體、間距尺度 */
@theme {
/* 顏色:對應 bg-xxx、text-xxx、border-xxx */
--color-brand-50: #f0f9ff;
--color-brand-500: #0ea5e9;
--color-brand-900: #0c4a6e;
/* 字體:對應 font-sans、font-mono */
--font-sans: "Inter", "Noto Sans TC", system-ui, sans-serif;
--font-mono: "JetBrains Mono", ui-monospace, monospace;
/* 間距尺度:對應 p-4、m-2、gap-6 */
--spacing-1: 0.25rem;
--spacing-2: 0.5rem;
--spacing-4: 1rem;
--spacing-8: 2rem;
/* 斷點:對應 sm:、md:、lg: */
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
}
這份 CSS 檔做了三件事:第一,@import "tailwindcss" 把 Tailwind 的 base、components、utilities 一次引入(v3 要分三行);第二,@theme 區塊宣告這個專案的設計 token,這些 token 會自動生成對應的 utility class(--color-brand-500 → bg-brand-500、text-brand-500 等);第三,不用 tailwind.config.js——所有設定都在 CSS 裡完成。
實務上有兩個注意事項:第一,@theme 區塊必須放在 @import "tailwindcss" 之後;第二,設計 token 的命名要遵循 Tailwind 慣例——顏色用 --color-xxx-yyy、字體用 --font-xxx、間距用 --spacing-xxx,這樣 Tailwind 才能正確產生 utility class。
用 utility class 組合元件樣式
Tailwind 的核心是「用 utility class 直接在 JSX 上描述樣式」。下面是一個按鈕元件的寫法:
// src/components/Button.tsx
// 用 Tailwind 4 寫按鈕元件
import type { ReactNode } from "react";
type ButtonProps = {
children: ReactNode;
variant?: "primary" | "secondary" | "ghost";
onClick?: () => void;
};
export function Button({ children, variant = "primary", onClick }: ButtonProps) {
// 基礎 class(所有 variant 都有)
const base = [
"inline-flex items-center justify-center",
"px-4 py-2 rounded-md",
"text-sm font-medium",
"transition-colors",
"focus-visible:outline-2 focus-visible:outline-offset-2",
];
// 根據 variant 決定顏色
const variantClass =
variant === "primary"
? "bg-brand-500 text-white hover:bg-brand-900"
: variant === "secondary"
? "bg-slate-200 text-slate-900 hover:bg-slate-300"
: "bg-transparent text-slate-700 hover:bg-slate-100";
return (
<button type="button" onClick={onClick} className={[...base, variantClass].join(" ")}>
{children}
</button>
);
}
這支 Button 展示四個 Tailwind 慣例:第一,className 用陣列 .join(" ") 串接多個條件 class,這比三元鏈好讀;第二,顏色 hover 狀態用 hover:bg-brand-900、focus 用 focus-visible:outline-2,Tailwind 把狀態當作前綴;第三,spacing scale 用 px-4 py-2(padding-x 1rem、padding-y 0.5rem)——這對應 @theme 裡的 --spacing-4;第四,rounded-md、text-sm、font-medium 等尺寸都來自設計 token,保證整個專案的視覺一致。
另一個常見的寫法是用 clsx 套件處理條件 class:
// 用 clsx 處理條件 class
import clsx from "clsx";
type CardProps = {
title: string;
active?: boolean;
};
export function Card({ title, active = false }: CardProps) {
return (
<div
className={clsx(
"rounded-lg border p-4 transition",
active ? "border-brand-500 bg-brand-50" : "border-slate-200 bg-white",
)}
>
<h3 className="text-lg font-semibold">{title}</h3>
</div>
);
}
clsx 的好處是處理「條件 class」更直觀:clsx("base", cond && "extra")。當條件多、需要組合時(例如「按鈕尺寸 × variant × disabled」),clsx 比陣列.join 容易讀。實務上大型專案幾乎都裝 clsx,Day 32 設計系統時會大量用到。
設計系統與設計 token
「設計系統」(design system)是「把視覺決策變成可重用的 token」的過程。它讓「按鈕的圓角」、「標題的字級」、「卡片之間的間距」這些「每頁都要重複決定」的事變成「從設定檔撈出來」。Tailwind 的 @theme 就是「設計系統的程式碼版本」。
一份完整的設計系統通常包含以下幾個維度:
- 顏色系統:品牌色(brand)、狀態色(success、warning、danger、info)、中性色(slate、gray)。每種顏色通常有 50–900 的 10 階色票。
- 字體階梯:字級(xs、sm、base、lg、xl、2xl、3xl)、行高(leading-tight、leading-relaxed)、字重(font-normal、font-medium、font-semibold、font-bold)。
- 間距尺度:4 倍數或 8 倍數的階梯(4px、8px、12px、16px、24px、32px、48px、64px),避免「自由發揮」的間距。
- 圓角尺度:none、sm、md、lg、full。圓角大會讓 UI 看起來「圓潤」,小會讓 UI 看起來「俐落」。
- 陰影尺度:sm、md、lg、xl。陰影給「卡片浮起來」的感覺,但不能過度。
把這套 token 寫進 @theme 之後,所有元件都「自動遵守」這份設計。例如:全站按鈕都用 rounded-md(中等圓角),就永遠不會出現「這頁按鈕圓一點、那頁方一點」的混亂。Day 32 會展開完整的設計系統實作,這裡先建立概念。
設計 token 的命名慣例
設計 token 的命名會直接影響 utility class 的可讀性。常見的命名模式:
/* 顏色:用途 + 色階 */
--color-brand-500: #0ea5e9;
--color-danger-700: #b91c1c;
--color-canvas: #ffffff; /* 背景色,不分階 */
/* 字體:用途 */
--font-sans: "Inter", system-ui, sans-serif;
--font-mono: "JetBrains Mono", monospace;
--font-serif: "Noto Serif TC", serif;
/* 字級:用途 + 大小 */
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
/* 圓角:用途 */
--radius-sm: 0.25rem;
--radius-md: 0.5rem;
--radius-lg: 1rem;
--radius-full: 9999px;
/* 陰影:用途 */
--shadow-sm: 0 1px 2px rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px rgb(0 0 0 / 0.1);
Tailwind 4 會把這些 token 自動轉成 utility class:--color-brand-500 → bg-brand-500、--radius-md → rounded-md、--shadow-lg → shadow-lg。命名越語意化、未來改色或換主題時越容易。例如品牌色叫 brand 而不是 blue,未來從藍色換成綠色時只要改 @theme 的值,不用改元件檔。
響應式與無障礙的 Tailwind 寫法
Tailwind 把響應式與無障礙的設計也內建到 utility class 裡。第一,響應式前綴:sm:、md:、lg:、xl: 對應到 @theme 裡的 breakpoint。寫法是「在某個斷點以上套用樣式」:
// src/components/Hero.tsx
// 響應式寫法:手機單欄、桌機雙欄
export function Hero() {
return (
<section className="grid grid-cols-1 gap-6 p-6 md:grid-cols-2 md:gap-8 md:p-12">
<div>
<h1 className="text-2xl font-bold md:text-4xl">預約管理系統</h1>
<p className="mt-2 text-slate-600 md:mt-4 md:text-lg">
用最少時間處理最多預約
</p>
</div>
<div className="flex items-center justify-center">
<img src="/hero.svg" alt="預約系統示意" className="w-full max-w-md" />
</div>
</section>
);
}
這段 Hero 區塊展示三個響應式技巧:grid-cols-1 md:grid-cols-2 在 md 以上變兩欄;p-6 md:p-12 在 md 以上加大 padding;text-2xl md:text-4xl 在 md 以上放大標題。手機優先(mobile first)是 Tailwind 的預設——沒寫前綴的 class 適用所有螢幕,加前綴代表「這個尺寸以上才套用」。
第二,無障礙前綴:focus-visible:、hover:、active:、disabled:。這些是 Tailwind 的狀態變體(variants),讓你直接用 class 寫互動樣式:
// src/components/SubmitButton.tsx
// 互動狀態 + 無障礙
type Props = {
isSubmitting: boolean;
children: React.ReactNode;
};
export function SubmitButton({ isSubmitting, children }: Props) {
return (
<button
type="submit"
disabled={isSubmitting}
className={[
"rounded-md bg-brand-500 px-4 py-2 text-white",
"hover:bg-brand-700",
"active:bg-brand-900",
"disabled:cursor-not-allowed disabled:opacity-50",
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-500",
].join(" ")}
>
{isSubmitting ? "送出中…" : children}
</button>
);
}
這段按鈕展示完整的互動 + 無障礙樣式:hover:bg-brand-700 滑鼠移過去變深;active:bg-brand-900 按下去更深;disabled:opacity-50 停用時變透明;focus-visible:outline-2 鍵盤 focus 時顯示外框(符合 WCAG 規範)。Day 28 會專門展開無障礙,這裡先記得「focus-visible:」這個 class 是寫鍵盤可用 UI 的好朋友。
完整實作:預約卡片元件
把今天學的東西整合起來:寫一個「預約卡片」元件,展示設計 token、響應式、條件 class 的綜合運用:
// src/features/booking/BookingCard.tsx
// 預約卡片:使用設計 token、響應式、條件 class
import clsx from "clsx";
import type { Booking, BookingStatus } from "../../types/booking";
type BookingCardProps = {
booking: Booking;
onCancel?: (id: number) => void;
};
// 狀態對應的視覺:背景色、邊框色、文字色
const statusStyles: Record<BookingStatus, string> = {
pending: "bg-amber-50 border-amber-300 text-amber-900",
confirmed: "bg-emerald-50 border-emerald-300 text-emerald-900",
cancelled: "bg-slate-100 border-slate-300 text-slate-600",
completed: "bg-sky-50 border-sky-300 text-sky-900",
};
const statusLabel: Record<BookingStatus, string> = {
pending: "待確認",
confirmed: "已確認",
cancelled: "已取消",
completed: "已完成",
};
export function BookingCard({ booking, onCancel }: BookingCardProps) {
return (
<article
className={clsx(
// 基礎樣式:圓角、邊框、padding、陰影
"rounded-lg border p-4 shadow-sm",
// 響應式:手機縱向、桌機橫向
"flex flex-col gap-3 md:flex-row md:items-center md:justify-between",
// 狀態對應的顏色(用設計 token)
statusStyles[booking.status],
)}
>
{/* 左側:預約資訊 */}
<div className="space-y-1">
<h3 className="text-lg font-semibold">
{booking.customerName}
<span className="ml-2 text-sm font-normal opacity-70">
#{booking.id}
</span>
</h3>
<p className="text-sm opacity-80">
{booking.customerPhone}・{booking.startAt}
</p>
<span className="inline-block rounded-full border px-2 py-0.5 text-xs">
{statusLabel[booking.status]}
</span>
</div>
{/* 右側:操作按鈕 */}
{onCancel && booking.status !== "cancelled" && (
<button
type="button"
onClick={() => onCancel(booking.id)}
className={clsx(
"rounded-md border px-3 py-1.5 text-sm",
"border-slate-300 bg-white text-slate-700",
"hover:bg-slate-50 active:bg-slate-100",
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-500",
)}
>
取消預約
</button>
)}
</article>
);
}
這支 BookingCard 展示八個設計觀念:第一,clsx 處理「基礎 + 響應式 + 狀態顏色」三組 class 的組合;第二,Record<BookingStatus, string> 確保四種狀態都有對應樣式,TypeScript 在編譯期就會抓出漏寫的狀態;第三,狀態樣式集中在 statusStyles 物件,未來改色只改一處;第四,flex flex-col md:flex-row 從手機直式切換到桌機橫式;第五,opacity-70、opacity-80 處理次要文字的視覺權重;第六,focus-visible:outline-2 讓鍵盤使用者看到 focus 框;第七,shadow-sm 讓卡片「浮起來」,但不過度;第八,整體只用 Tailwind class,沒有額外的 CSS 檔——設計 token 改了就全站跟著改。
常見錯誤與踩雷
第一次用 Tailwind 4 最常踩的雷有四個。第一個是「設定沒生效」。症狀:@theme 寫了 --color-brand-500 但 bg-brand-500 沒出現。原因可能是:@theme 寫在 @import "tailwindcss" 之前、或是命名不符合慣例(少了 color-、spacing- 等前綴)。修法是確認順序、檢查命名、重新編譯。
第二個是「忘記 utility class 不會被 purge」。Tailwind v4 預設會掃描專案內所有檔案,找出實際用到的 utility class。如果某個 class 是「執行時動態組合」(例如 className={`p-${size}`}),它不會被識別。修法是在 @source 區塊明確列出會動態產生的檔案,或改用 safelist:
/* src/styles/app.css */
/* 明確告訴 Tailwind 掃描哪些檔案 */
@import "tailwindcss" source("../src/**/*.{ts,tsx}");
第三個是「忘記寫斷點前綴」。新手寫 md:grid-cols-2 時以為「手機會變兩欄」,實際上 Tailwind 是 mobile-first——md: 代表「md 以上才兩欄」,手機是一欄。如果要「手機兩欄、桌機一欄」要用 max-md:grid-cols-2 這類反向變體。
第四個是「className 太長難讀」。當元件樣式很複雜,把所有 class 寫在 JSX 上會讓程式碼很亂。修法有兩個:把基礎 class 抽成常數(const base = "...");用 clsx 處理條件組合;或用 @apply 在 CSS 裡定義元件 class:
/* 在 CSS 裡定義可重用的元件 class */
@layer components {
.btn-primary {
@apply inline-flex items-center justify-center rounded-md bg-brand-500 px-4 py-2 text-white hover:bg-brand-700;
}
}
但要注意:@apply 寫太多會讓你「重新發明 CSS Modules」——失去了 Tailwind 集中管理的優勢。建議只在「跨多個元件共用、又有條件差異」的情境用 @apply,否則直接寫 className。
效能與實務提醒
Tailwind 4 的編譯速度比 v3 快很多(用 Rust 引擎),但「class 名稱很長」這件事還是會讓 HTML 變肥。實務上有兩個技巧可以降低 bundle size:第一,使用 PurgeCSS 機制(v4 內建),它會自動把沒用到的 class 從最終 CSS 裡刪掉,確保 CSS 檔案只包含實際用到的部分;第二,避免在元件裡寫 style={{...}} 內嵌樣式,這會繞過 Tailwind 的 purge 機制。
另一個實務提醒是「設計 token 的顏色要符合對比」。WCAG AA 規範要求文字與背景的對比至少 4.5:1(一般文字)或 3:1(大字)。這不是「設計感」的問題,是「無障礙」的問題——視障、色弱、銀髮使用者都需要足夠對比才能讀。Tailwind 的色票(slate-900 配 slate-50)已經符合對比,但自訂顏色(--color-brand-500)要自己驗證。可以用 Stark、WebAIM Contrast Checker 等工具測試。Day 28 與 Day 42 會再展開無障礙細節。
第三個提醒是「不要過度使用響應式 class」。每加一個 md:、lg:,CSS 體積就會膨脹。如果設計稿只有「手機 / 桌機」兩種尺寸,就用 md: 一個斷點就好;如果有「手機 / 平板 / 桌機 / 大螢幕」四種,再用 sm: md: lg: xl:。盲目堆斷點會讓 CSS 變肥、也讓元件難以預測。Day 25 會展開 Core Web Vitals 與 bundle size 的取捨。
最後一個重點是「設計系統要先內部約定」。寫 @theme 之前,先和設計師(如果有的話)或團隊對齊:色票要哪些?字級要幾階?間距尺度是 4 倍數還是 8 倍數?圓角大小?陰影規範?把這些決定寫進 design token 之後,所有元件都遵守,整個專案的視覺就會一致。沒有先約定就寫 Tailwind,最後會出現「這頁用 slate、那頁用 gray、第三頁自訂顏色」的混亂。Day 32 會展開完整的設計系統建置流程。
把設計 token 變成 preset:團隊共用
如果整個團隊要把 Tailwind 的設計 token 統一管理,最常見的做法是「把 @theme 區塊抽成一個獨立的 CSS 檔,再讓各專案 import」。這個獨立檔通常叫 tailwind-preset.css,放在公司內部的 npm 套件或 monorepo 的共享目錄裡:
/* packages/design-system/tailwind-preset.css */
/* 團隊共用的 Tailwind 4 設計 token */
@import "tailwindcss";
@theme {
/* 品牌色票:對應 bg-brand-xxx、text-brand-xxx */
--color-brand-50: #f0f9ff;
--color-brand-100: #e0f2fe;
--color-brand-500: #0ea5e9;
--color-brand-700: #0369a1;
--color-brand-900: #0c4a6e;
/* 狀態色:對應 bg-success、bg-warning、bg-danger */
--color-success: #10b981;
--color-warning: #f59e0b;
--color-danger: #ef4444;
/* 字體階梯:對應 text-sm、text-lg 等 */
--font-sans: "Inter", "Noto Sans TC", system-ui, sans-serif;
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
/* 間距尺度(4 倍數) */
--spacing-1: 0.25rem;
--spacing-2: 0.5rem;
--spacing-4: 1rem;
--spacing-8: 2rem;
--spacing-12: 3rem;
/* 圓角 */
--radius-sm: 0.25rem;
--radius-md: 0.5rem;
--radius-lg: 1rem;
--radius-full: 9999px;
/* 斷點:對應 sm:、md:、lg: 前綴 */
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
}
各專案的 app.css 只要一行就能繼承整套 token:
// src/styles/app.css
// 載入團隊 preset + 專案自訂的覆寫
@import "@company/design-system/tailwind-preset.css";
@theme {
/* 專案自訂:覆寫部分 token */
--color-brand-500: #14b8a6; /* 專案 A 用綠色品牌 */
}
這種 preset 模式讓團隊的視覺語言集中管理:設計師改色票時只需要改 preset,各專案 npm update 後視覺就統一更新。實務上大型組織(電商、金融、SaaS)幾乎都會建立自己的 design system 套件,避免每個專案各寫各的 Tailwind 設定。Day 32 會展開完整的設計系統建置流程,包含 token、preset、Storybook、CSS 變數怎麼互通。
另一個延伸主題是「元件庫」。如果 preset 是「視覺字典」,那元件庫就是「視覺單字」。shadcn/ui 是 2026 年最主流的 React 元件庫之一:它不是傳統的 npm 套件(裝完才能用),而是「把元件原始碼複製到你的專案裡」(copy-paste)。這種模式讓你能 100% 客製化元件樣式,又不用從零開始寫。當 Tailwind preset 搭配 shadcn/ui 時,整個專案的視覺可以快速達成「品牌一致 + 工程一致」的雙重目標。
最後回顧一下今天的學習地圖:我們從樣式方案的分類開始,理解 Tailwind 4 為什麼成為主流;接著動手寫 CSS-first 設定,把設計 token 變成可重用的 utility class;再學會 utility class 與元件搭配的慣例(條件 class、clsx);設計系統的概念也建立了(色票、字級、間距尺度);最後用完整的預約卡片把所有觀念串起來。這套方法論是 Day 32 設計系統章節的基礎,Day 28 無障礙、Day 25 效能優化也都會反覆提到。寫完今天的範例,你應該已經能獨立產出「有設計感、有系統性」的前端畫面——這是後端工程師跨足全端開發的一個重要里程碑。
小結
今天我們把前端樣式方案拆開來看:分類與取捨、Tailwind 4 的 CSS-first 設定、utility class 與元件搭配、條件 class 的處理(clsx)、設計系統與 design token 的概念、響應式與無障礙的 Tailwind 寫法。最後用一個完整的「預約卡片」元件把所有觀念串起來。明天 Day 9 進入 useEffect:副作用的處理、依賴陣列的規則、cleanup 的角色,以及「為什麼 useEffect 不是生命週期方法」。Day 9 結束時,你的元件就能跟外部世界(API、計時器、localStorage)互動了。
結語
明天,我們會把 useEffect 一次學完:什麼是副作用、effect 的 callback 什麼時候執行、依賴陣列的規則、cleanup 為什麼重要、什麼情境根本不該用 useEffect。Day 9 是 React Hooks 的關鍵轉折——從「純函式元件」跨到「能跟外部世界溝通的元件」。接著 Day 10 進入自訂 Hook,把 useEffect、useState 等組合起來變成可重用的積木。
延伸資源
- Tailwind CSS 官方(v4 說明):
https://tailwindcss.com/docs/installation - Tailwind CSS 官方〈Theme variables〉:
https://tailwindcss.com/docs/theme - Tailwind CSS 官方〈Responsive design〉:
https://tailwindcss.com/docs/responsive-design - shadcn/ui 元件庫(Tailwind 4 + Radix UI):
https://ui.shadcn.com/ - MDN〈CSS Custom Properties〉:
https://developer.mozilla.org/zh-TW/docs/Web/CSS/--*
留言
張貼留言