Skip to main content

tailwind-v4

Tailwind CSS v4 styling with @theme, @utility, and @custom-variant directives. Use when styling components, configuring design tokens, adding custom utilities, migrating from v3, or converting hardcoded colors to theme tokens. Enforces token-first design flow: look up existing @theme tokens before reaching for arbitrary values, and extend @theme when a design exceeds the token range rather than sprinkling ad-hoc `[Npx]` / `[#hex]` values.

跳到安装

来源信息

仓库
recca0120/code-quest
最近来源活动
2026年5月13日 16:19
检测到的 SKILL.md 语言
多语言混合
星标
11
分支
2

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
tailwind-v4
description
Tailwind CSS v4 styling with @theme, @utility, and @custom-variant directives. Use when styling components, configuring design tokens, adding custom utilities, migrating from v3, or converting hardcoded colors to theme tokens. Enforces token-first design flow: look up existing @theme tokens before reaching for arbitrary values, and extend @theme when a design exceeds the token range rather than sprinkling ad-hoc `[Npx]` / `[#hex]` values.
# Tailwind CSS v4 ## Overview Tailwind v4 is CSS-first: no `tailwind.config.js`, no PostCSS config. Everything lives in CSS via directives. ## Setup (Vite) ```bash pnpm add tailwindcss @tailwindcss/vite ``` ```ts // vite.config.ts import tailwindcss from '@tailwindcss/vite' export default defineConfig({ plugins: [react(), tailwindcss()] }) ``` ```css /* App.css */ @import "tailwindcss"; ``` No `content` globs needed — auto-detected. No autoprefixer — Lightning CSS handles it. ## v3 → v4 Key Changes | v3 | v4 | |---|---| | `tailwind.config.js` | `@theme` in CSS | | `@tailwind base/components/utilities` | `@import "tailwindcss"` | | `darkMode: 'class'` | `@custom-variant dark` | | `content: [...]` | Auto-detected | | PostCSS required | `@tailwindcss/vite` plugin | | Container query plugin | Built-in `@container` | ## Design flow: token-first 設計 UI 時**先看 design token,再考慮 utility**。不要遇到設計稿的值就寫 arbitrary(`text-[13px]` / `bg-[#3a7]` / `p-[7px]`)。 **正確順序(寫 JSX 前跑一遍):** 1. **先查既有 token** — 需要的顏色 / 字級 / 間距 / radius / shadow / z-index 是否已經在 `@theme` 或內建 scale? - 顏色:`grep "^\s*--color-" apps/web/src/App.css` - Spacing / text / z:用「優先內建 utility」表對照 2. **差 1–2px / 1–2 階 → 就近取** — design system 的一致性 > pixel-perfect。`13px` 取 `text-xs` 或 `text-sm`、`#3a7d5e` 取 `text-success`。 3. **超出既有 token 範圍 → 先擴 token,不要寫 arbitrary** - 新色階:加 `--color-X` 到 `@theme` - 新語意層級(z-index、muted tier、shadow):加對應 `--*` token - 新可重用效果:抽 `@utility` 4. **只有這些 arbitrary 值是合法的**(見下方「Arbitrary value 正當情境」): - `calc(...)` 計算值 - 引用動態 CSS variable - 刻意 off-grid 且有清楚理由(icon 尺寸、對齊特定視覺錨點) **不要這樣做**: ```tsx // ❌ 為了新設計稿直接寫 arbitrary <div className="bg-[#2a4a6e] text-[13px] p-[7px] shadow-[0_2px_8px_rgba(0,0,0,0.4)]" /> // ✅ 先把需要的值加進 @theme,再用 semantic utility // App.css: --color-surface-sunken: oklch(...); --shadow-card: 0 2px 8px rgba(0,0,0,0.4); <div className="bg-surface-sunken text-xs p-2 shadow-card" /> ``` **特別 callout — literal-pixel arbitraries**: **任何** `\w+-[Npx]`(`text-[13px]` / `h-[38px]` / `max-h-[480px]` / `max-w-[180px]` 等)**明確禁止**。 收斂規則: - text size: `text-[1Npx]` → `text-xs` (12px) 或 `text-2xs` (10px) chip token - `text-2xs` 限:uppercase tracked chip badges、section-heading utility(`section-label` 用 `font-size: var(--text-2xs)` 而非 hardcoded `10px`) - body / link / hint / dialog 文字:用 `text-xs`,**不要**用 `text-2xs` - height / width / spacing: 走 Tailwind v4 整數 spacing — `h-9` (36)、`max-h-120` (480)、`max-w-45` (180) 等。專案 `--spacing: 0.21875rem`,所以 `N × 0.21875rem` = N × 3.5px(注意:不是預設的 4px) - chip / badge 視覺重量靠 `tracking-wider` + `bg-x/10` + 弱化色控制,不是縮字 - 沒有 px arbitrary 例外——`backdrop-blur` 用 Tailwind 最小 `xs` (4px),其他都 N × 3.5px 就近取 **注意:本專案 `--spacing: 0.21875rem`**(密度 axis 縮 87.5%),所以 Tailwind 預設「N = 4px」**不適用**——本專案 `N = 3.5px`: - `h-9` = 31.5px、`h-10` = 35px、`h-11` = 38.5px - `max-w-50` = 175px、`max-w-51` = 178.5px - `max-h-120` = 420px(不是 480px)—— 4 處重複的 480px dialog body 抽 `@utility max-h-dialog-body { max-height: 30rem; }`,30rem 直接用 rem 不靠 spacing 計算 guard test: `apps/web/src/utils/__tests__/no-arbitrary-utility.test.ts` 掃 components + stories,PR 加新 `\w+-[Npx]` 時擋下來。 **為什麼**: - arbitrary 值無法透過 `data-theme` / `data-font` / `data-density` axis 重載,使用者切主題或調密度時視覺破版。 - token 有語意(`accent` / `surface-sunken` / `shadow-card`);arbitrary 只是 raw value,下一個讀者看不出意圖。 - 設計系統收斂:同一個顏色 / 尺寸出現在 5 個地方,都指向同一 token,之後微調改一處即可。 **Review 時的判準**:看到 `[...]` 先問「為什麼不是 token?」——如果答案是「設計稿就這樣」而沒有 arbitrary 正當情境,先調 token。 ## Theme Configuration (`@theme`) ```css @theme { --color-brand: oklch(0.65 0.22 260); --font-display: "Cal Sans", sans-serif; --breakpoint-xs: 30rem; } ``` - `@theme` → generates CSS variables AND utility classes - `:root` → CSS variables only, no utilities - `--color-*: initial;` → wipe namespace defaults - `@theme inline` → for tokens referencing other variables - `@theme static` → force-generate even if unused ## Custom Utilities & Variants ```css @utility scrollbar-hidden { &::-webkit-scrollbar { display: none; } } @custom-variant dark (&:where(.dark, .dark *)); ``` ## Styling approach priority 1. **Tailwind utility classes in JSX** — default 2. **`@theme` tokens** — new color/shadow/font value (generates utility + CSS var) 3. **`@utility` directive** — reusable effect with modifier support (`hover:`, `dark:`) 4. **`@layer base` with `[data-*]` selectors** — third-party DOM you can't add className to (Markdown, CMS) `@apply` is rarely the right choice. For React/Vue components, keep utility classes directly in JSX (markup + styles together). ## Responsive Design - **Viewport** (`md:`, `lg:`) → page layout - **Container** (`@container` + `@md:`) → component internals ```html <div class="@container"> <div class="flex flex-col @md:flex-row"> </div> ``` ## Dark Mode / Theme axis ```css /* OS preference (default) */ @import "tailwindcss"; /* Class-based toggle */ @custom-variant dark (&:where(.dark, .dark *)); /* Data attribute (project convention) */ @custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *)); ``` ## Project-specific: preference axes 此專案用 `:root[data-*]` 管理使用者偏好,每個 axis 影響不同 utility 群: ```css :root[data-theme="dark|light"] /* 重載 --color-* 值 → 影響 bg-/text-/border- utility */ :root[data-font="sm|md|lg"] /* font-size: 14/16/18px → rem-based utility 等比縮放 */ :root[data-density="comfortable|compact"] /* 重載 --spacing → p/m/gap utility 等比縮放 */ ``` **寫 utility 時的 axis 考量:** - ✅ **rem-based** (Tailwind 內建 utility 如 `text-xs` / `p-4` / `w-6`) 會跟著 `data-font` 縮放 - ✅ **em-based** (`tracking-wider` / `text-[0.9em]`) 會跟著父元素字級縮放 - ⚠️ **px arbitrary** (`text-[13px]` / `p-[10px]` / `gap-[6px]`) **不縮放**,`data-font=lg` 時字放大但 padding 不變,比例跑掉 包文字的元素(button / chip / badge)優先用 rem-based utility,避免 axis 切換時視覺破版。 ## 優先內建 utility 遇到 `[Npx]` / `[Nrem]` / `[rgba(...)]` 先檢查能否改內建。差 1-2px 就近取(設計系統一致性 > pixel-perfect)。 ### Spacing(`p/m/w/h/gap/top/left/right/bottom`) 預設 `--spacing: 0.25rem`,所以 `N` = 4N px。Tailwind v4 支援任意整數:`w-160` = 640px。半步只有 `0.5/1.5/2.5/3.5`。 | px | Tailwind | px | Tailwind | |---|---|---|---| | 2 | `0.5` | 24 | `6` | | 4 | `1` | 32 | `8` | | 6 | `1.5` | 40 | `10` | | 8 | `2` | 48 | `12` | | 10 | `2.5` | 64 | `16` | | 12 | `3` | 96 | `24` | | 14 | `3.5` | 任意 4N | `N` | | 16 | `4` | 200 | `50` | | 20 | `5` | 640 | `160` | ### 其他內建 scale - **Rounded**:`rounded-sm`(2) / `rounded`(4) / `rounded-md`(6) / `rounded-lg`(8) / `rounded-xl`(12) / `rounded-2xl`(16) / `rounded-full` - **Text size**:`text-xs`(12) / `text-sm`(14) / `text-base`(16) / `text-lg`(18) / `text-xl`(20) / `text-2xl`(24) — 不使用自訂 token - **Tracking**(em-based):`tracking-tighter`(-0.05em) / `tight`(-0.025) / `normal`(0) / `wide`(0.025) / `wider`(0.05) / `widest`(0.1) - **Leading**:`leading-none`(1) / `tight`(1.25) / `snug`(1.375) / `normal`(1.5) / `relaxed`(1.625) / `loose`(2) ### Arbitrary value 正當情境 - 計算值:`max-h-[calc(100vh-64px)]` - 動態 CSS variable:`bg-[linear-gradient(...var(--X)...)]` - 沒有對應的刻意 off-grid 值(如 `w-[26px]` icon — review 時確認是否真需要 26 而非 24/28) ## Opacity modifier(v4 推薦) `--color-X` 是 hex token 時,直接用 opacity modifier 產生半透明: ```tsx <div className="bg-accent/10" /> // ✅ v4 modifier <div className="text-text-muted/60" /> // ✅ 3-tier opacity ``` ### 3-tier text opacity(專案慣例) 文字 muted 層級收斂到三階,需要更細層級時優先重新評估 hierarchy: | class | 用途 | |---|---| | `text-text-muted` | primary muted(次要文字) | | `text-text-muted/60` | faint(placeholder、disabled-ish) | | `text-text-muted/40` | super faint(gutter、decorator) | 早期 pre-v4 的 `rgba(var(--color-X-rgb), 0.N)` 模式改用 modifier: ```tsx <div className="bg-[rgba(var(--color-accent-rgb),0.1)]" /> // legacy,改 bg-accent/10 ``` ## cn() helper(clsx + twMerge) 專案 `utils/cn` 包 `twMerge(clsx(...))`,提供: - `clsx` — conditional className 組合 - `twMerge` — **自動解決 Tailwind class 衝突** ```tsx // Button 內部: cn('px-3 py-1', className) <Button className="px-6" /> // 結果 "py-1 px-6"(px-3 被覆蓋) <Button className="bg-red-500" /> // primary bg-accent 被覆蓋 ``` Component composition 放心用 `className` prop override,不用擔心 CSS 順序。 ## 此專案已有的 @utility (`apps/web/src/App.css`) - `dialog-viewport` — `max-w: calc(100vw - 2rem); max-h: calc(100vh - 4rem);` - `floating-popover-lg` / `floating-popover-sm` — gradient bg + layered shadow for CommandPalette / FilterPopover - `inset-border` — `box-shadow: inset 0 0 0 1px var(--color-border);` 不佔 layout 空間的 1px 邊框 抽新 `@utility` 的時機:**相同 pattern 重複 3+ 處**。 ## Semantic z-index tiers 用 theme token 對應語意層級: | Tier | Value | 用途 | |---|---|---| | `z-raised` | 1 | in-content stacking(dot over line, icon over bg) | | `z-sticky` | 10 | sticky/overlay within a section | | `z-float` | 30 | floating buttons(scroll-to-bottom) | | `z-overlay` | 40 | backdrop behind modals |
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看