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
آخر نشاط في المصدر
١٣ مايو ٢٠٢٦ في ١٦:١٩
لغة SKILL.md المكتشفة
لغات متعددة
النجوم
١١
التفرعات
٢

خيارات التثبيت

يُحدَّد 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