Tailwind CSS は、ユーティリティクラスを HTML に直接書き込んでデザインを構築する Utility-First の CSS フレームワークです。2024 年末にリリースされた v4.0 では、Rust 製の高速エンジン Lightning CSS への移行、@theme による CSS ネイティブのデザイントークン、設定ファイル不要の運用、そして shadcn/ui との連携強化など、開発体験が大きく進化しました。本記事では、Tailwind CSS v4 を実プロジェクトで使いこなすための 40 個以上のコード例 をもとに、入門から実践、パフォーマンス最適化までを一気通貫で解説します。
- 1. Tailwind CSS v4 とは — Utility-First の哲学とエンジン刷新
- 2. インストール — Vite / Next.js / 純 HTML での導入
- 3. @theme — CSS ネイティブのデザイントークン
- 4. Utility 早見表 — レイアウト・余白・タイポ
- 5. 色・ボーダー・角丸・シャドウ
- 6. インタラクション — Hover / Focus / Group / Peer
- 7. ダークモード — media と class、v4 の selector 戦略
- 8. レスポンシブと container query
- 9. アニメーション・トランジション・3D 変形
- 10. アスペクト比・グラデーション・backdrop blur
- 11. arbitrary value と任意プロパティ
- 12. @apply とコンポーネント抽象化
- 13. shadcn/ui との連携 — モダン React UI のスタンダード
- 14. cva / clsx / tailwind-merge — クラス管理の三種の神器
- 15. プラグインと公式エクステンション
- 16. CSS-in-JS との比較 — なぜ Tailwind なのか
- 17. パフォーマンス最適化 — JIT と未使用クラス除去
- 18. パフォーマンス連携 — Web Vitals を悪化させない使い方
- 19. 実践: ダッシュボードカードを作る
- 20. まとめ — Tailwind CSS v4 で得られる開発体験
1. Tailwind CSS v4 とは — Utility-First の哲学とエンジン刷新
1-1. Utility-First の基本思想
従来の CSS は card や btn-primary のような「セマンティックな命名」を中心に組み立てていました。一方 Tailwind は p-4(padding 1rem)、text-lg(font-size 1.125rem)、bg-blue-500 といった「役割が一目でわかる短い utility」を HTML に並べていく設計を採用しています。これによりクラス名命名の悩みが消え、HTML を見るだけで見た目が把握でき、未使用 CSS が自動的に消える(JIT)というメリットが得られます。
<!-- 従来の CSS -->
<div class="card">
<h2 class="card-title">Hello</h2>
</div>
<!-- Tailwind の Utility-First -->
<div class="p-6 bg-white rounded-xl shadow-md">
<h2 class="text-xl font-bold text-slate-800">Hello</h2>
</div>
1-2. v4 の何が変わったか
v4 はメジャーアップデートとして、ビルドエンジンを JavaScript ベースの PostCSS パイプラインから Rust 製 Lightning CSS に置き換えました。コールドスタートは最大 5 倍、フルビルドは 100 倍以上速くなった事例もあります。また、tailwind.config.js がオプション化され、CSS だけで設定が完結 するようになりました。
/* v3 までの導入 */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* v4 はワンライナー */
@import "tailwindcss";
1-3. v3 と v4 の比較表
項目 v3 v4
-----------------+---------------------------+-----------------------------
エンジン PostCSS + JS Lightning CSS (Rust)
設定 tailwind.config.js (必須) @theme(CSS 内、任意)
ビルド速度 100% (基準) 約 3〜10 倍高速
カラー API rgb(var(--c) / <alpha>) oklch(...) ネイティブ
コンテナクエリ plugin 必要 標準内蔵 (@container)
3D 変形 未対応 rotate-x-* / translate-z-*
2. インストール — Vite / Next.js / 純 HTML での導入
2-1. Vite + React への導入
Vite ベースのプロジェクトでは、v4 専用の公式プラグイン @tailwindcss/vite を使うのが推奨です。PostCSS を介さない分、設定が最小限になります。
# 依存追加
npm install -D tailwindcss @tailwindcss/vite
# vite.config.ts
import { defineConfig } from "vite"
import tailwindcss from "@tailwindcss/vite"
import react from "@vitejs/plugin-react"
export default defineConfig({
plugins: [react(), tailwindcss()],
})
/* src/index.css — これだけ */
@import "tailwindcss";
// src/main.tsx
import "./index.css"
import { createRoot } from "react-dom/client"
import App from "./App"
createRoot(document.getElementById("root")!).render(<App />)
2-2. Next.js (App Router) への導入
Next.js 15 系では PostCSS 経由が標準フローです。@tailwindcss/postcss を使います。
# インストール
npm install -D tailwindcss @tailwindcss/postcss postcss
# postcss.config.mjs
export default {
plugins: {
"@tailwindcss/postcss": {},
},
}
/* app/globals.css */
@import "tailwindcss";
/* app/layout.tsx */
import "./globals.css"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ja">
<body className="bg-slate-50 text-slate-900 antialiased">{children}</body>
</html>
)
}
2-3. CDN(プロトタイピング用)
「数分でモックを作りたい」用途には CDN が便利です。本番には使いません。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<script src="https://cdn.tailwindcss.com/4.0.0"></script>
</head>
<body class="bg-gradient-to-br from-sky-400 to-indigo-600 min-h-screen grid place-items-center">
<h1 class="text-5xl font-black text-white drop-shadow-lg">Hello Tailwind v4</h1>
</body>
</html>
3. @theme — CSS ネイティブのデザイントークン
3-1. @theme の基本
v4 では tailwind.config.js を書かなくても、CSS の @theme ブロックでブランドカラーやフォント、ブレイクポイントを宣言できます。Tailwind はこれを CSS 変数として展開し、同時にユーティリティクラスも自動生成します。
/* app/globals.css */
@import "tailwindcss";
@theme {
--color-brand-50: oklch(0.97 0.02 250);
--color-brand-500: oklch(0.62 0.18 250);
--color-brand-900: oklch(0.25 0.10 250);
--font-display: "Inter", "Hiragino Sans", sans-serif;
--breakpoint-3xl: 120rem;
}
<!-- 上の @theme 定義から自動でクラスが生まれる -->
<button class="bg-brand-500 hover:bg-brand-900 text-white font-display px-4 py-2 rounded-lg">
Buy Now
</button>
<div class="3xl:grid-cols-6 grid grid-cols-2">...</div>
3-2. oklch カラーの恩恵
v4 は OKLCH(知覚均等な色空間)を標準採用。明度を 0.62 から 0.50 に下げると、人間の目にも均等に暗く見えます。これにより、ブランドカラーの濃淡パレットを自動生成しやすくなります。
@theme {
/* hue を固定して明度だけ動かす → 自然なグラデーション */
--color-acc-100: oklch(0.95 0.05 25);
--color-acc-300: oklch(0.82 0.12 25);
--color-acc-500: oklch(0.65 0.20 25);
--color-acc-700: oklch(0.48 0.18 25);
--color-acc-900: oklch(0.30 0.10 25);
}
3-3. レガシー設定との併用
「既存の tailwind.config.js を活かしつつ移行したい」場合は @config ディレクティブで読み込めます。
@import "tailwindcss";
@config "../tailwind.config.js";
4. Utility 早見表 — レイアウト・余白・タイポ
4-1. Flex レイアウト
<!-- ヘッダー: 左ロゴ・右ナビ -->
<header class="flex items-center justify-between px-6 py-4 border-b">
<a class="text-xl font-bold tracking-tight">Logo</a>
<nav class="flex gap-6 text-sm font-medium text-slate-600">
<a href="#" class="hover:text-slate-900">Docs</a>
<a href="#" class="hover:text-slate-900">Pricing</a>
<a href="#" class="hover:text-slate-900">Blog</a>
</nav>
</header>
4-2. Grid レイアウト
<!-- 12 カラム → md 以上で 3 列カードグリッド -->
<section class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
<article class="p-6 bg-white rounded-2xl shadow">Card 1</article>
<article class="p-6 bg-white rounded-2xl shadow">Card 2</article>
<article class="p-6 bg-white rounded-2xl shadow">Card 3</article>
</section>
4-3. Padding / Margin の刻み
<!--
p-0 → 0
p-1 → 0.25rem (4px)
p-2 → 0.5rem (8px)
p-4 → 1rem (16px)
p-6 → 1.5rem (24px)
p-8 → 2rem (32px)
p-px → 1px
-->
<div class="p-4 md:p-6 lg:p-8">レスポンシブな余白</div>
<div class="space-y-4">
<p>子要素同士に縦 1rem 空ける</p>
<p>ラッパーで一括指定できる</p>
</div>
4-4. テキスト関連
<h1 class="text-4xl font-extrabold tracking-tight text-balance">
text-balance で見出しの改行をきれいに整える
</h1>
<p class="text-base/7 text-slate-700 text-pretty">
text-base/7 は font-size: 1rem / line-height: 1.75rem の同時指定。
text-pretty で 1 単語だけ行頭に残る「未亡人」を防ぐ。
</p>
5. 色・ボーダー・角丸・シャドウ
5-1. 背景・テキストカラー
<div class="bg-slate-900 text-slate-100 px-6 py-4 rounded-xl">
<span class="text-emerald-400 font-mono">$ npm run dev</span>
</div>
5-2. ボーダーと角丸
<input
type="text"
class="border border-slate-300 rounded-lg px-3 py-2
focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200
outline-none transition-colors"
placeholder="メールアドレス" />
5-3. シャドウ
<!-- 標準シャドウから巨大ドロップシャドウまで -->
<div class="shadow-sm rounded p-4 bg-white">sm</div>
<div class="shadow rounded p-4 bg-white">default</div>
<div class="shadow-md rounded p-4 bg-white">md</div>
<div class="shadow-lg rounded p-4 bg-white">lg</div>
<div class="shadow-xl rounded p-4 bg-white">xl</div>
<div class="shadow-2xl rounded p-4 bg-white">2xl</div>
<div class="shadow-[0_30px_80px_-10px_rgba(99,102,241,0.5)] p-4 bg-white">
arbitrary value のカスタムシャドウ
</div>
6. インタラクション — Hover / Focus / Group / Peer
6-1. Hover / Focus / Active
<button class="
px-5 py-2.5 rounded-lg
bg-indigo-600 text-white font-semibold
hover:bg-indigo-500
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600
active:bg-indigo-700
disabled:bg-slate-300 disabled:cursor-not-allowed
transition-colors
">
Subscribe
</button>
6-2. group — 親 hover で子の状態を変える
<a href="#" class="group flex items-center gap-3 p-4 rounded-xl hover:bg-slate-100">
<span class="size-10 rounded-full bg-indigo-100 group-hover:bg-indigo-600 transition-colors"></span>
<span class="font-medium text-slate-700 group-hover:text-indigo-700 transition-colors">
Open Project
</span>
</a>
6-3. peer — 兄弟要素の状態を反映
<label class="block">
<span class="text-sm font-medium">Email</span>
<input type="email" required
class="peer mt-1 w-full rounded-md border-slate-300
focus:border-indigo-500 invalid:border-rose-500" />
<span class="hidden peer-invalid:block text-sm text-rose-600 mt-1">
正しいメール形式で入力してください
</span>
</label>
6-4. data-* バリアント
<!-- Radix UI などの data 属性をフックする -->
<div
data-state="open"
class="data-[state=open]:opacity-100 data-[state=closed]:opacity-0 transition-opacity">
Toast 表示
</div>
7. ダークモード — media と class、v4 の selector 戦略
7-1. media 戦略(OS 連動)
<!-- 何もしなくても dark: が prefers-color-scheme に追従 -->
<div class="bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-100">
Hello
</div>
7-2. class 戦略(ユーザー切替式)
v4 では @variant を CSS 側で書くだけで切替戦略を変えられます。
@import "tailwindcss";
/* html.dark がついたら dark: が有効になる */
@variant dark (&:where(.dark, .dark *));
// theme-toggle.ts
export function toggleTheme() {
const root = document.documentElement
const next = root.classList.toggle("dark")
localStorage.setItem("theme", next ? "dark" : "light")
}
// 起動時に復元
const saved = localStorage.getItem("theme")
if (saved === "dark") document.documentElement.classList.add("dark")
7-3. data-theme 戦略(マルチテーマ)
@variant dark (&:where([data-theme=dark], [data-theme=dark] *));
@variant sepia (&:where([data-theme=sepia], [data-theme=sepia] *));
/* HTML 側 */
<html data-theme="sepia">
<body class="bg-white sepia:bg-amber-50 dark:bg-slate-900">...</body>
</html>
8. レスポンシブと container query
8-1. ブレイクポイント
sm → 640px
md → 768px
lg → 1024px
xl → 1280px
2xl → 1536px
<div class="
grid grid-cols-1
sm:grid-cols-2
md:grid-cols-3
lg:grid-cols-4
2xl:grid-cols-6
gap-4
">...</div>
8-2. max-* バリアント(モバイル優先の逆方向)
<!-- 768px 未満だけ縦並びにしたい -->
<ul class="flex gap-4 max-md:flex-col">
<li>1</li><li>2</li><li>3</li>
</ul>
8-3. container query
v4 では @container がコアに統合されました。「画面幅ではなく親要素の幅でレイアウトを切り替える」真のコンポーネント単位レスポンシブが可能です。
<aside class="@container w-1/3">
<div class="grid @md:grid-cols-2 @lg:grid-cols-3 gap-4">
<div>...</div>
<div>...</div>
<div>...</div>
</div>
</aside>
9. アニメーション・トランジション・3D 変形
9-1. transition の基本
<button class="
px-4 py-2 bg-amber-500 text-white rounded-lg
hover:bg-amber-600 hover:-translate-y-0.5 hover:shadow-lg
active:translate-y-0 active:shadow
transition duration-200 ease-out
">Click me</button>
9-2. キーフレームアニメ
@theme {
--animate-blob: blob 7s infinite;
}
@keyframes blob {
0% { transform: translate(0,0) scale(1); }
33% { transform: translate(30px,-50px) scale(1.1); }
66% { transform: translate(-20px,20px) scale(0.9); }
100% { transform: translate(0,0) scale(1); }
}
<div class="size-72 bg-purple-400 rounded-full mix-blend-multiply filter blur-2xl animate-blob"></div>
9-3. transform / rotate / scale
<div class="rotate-3 hover:rotate-0 scale-95 hover:scale-100 transition-transform">
カードホバーで真っ直ぐに
</div>
<!-- v4 の 3D 変形 -->
<div class="perspective-distant">
<div class="transform-3d rotate-x-12 rotate-y-12 transition-transform hover:rotate-y-0">
3D Card
</div>
</div>
10. アスペクト比・グラデーション・backdrop blur
10-1. aspect-ratio
<div class="aspect-video w-full bg-slate-100 rounded-xl overflow-hidden">
<iframe class="size-full" src="https://www.youtube.com/embed/xxx"></iframe>
</div>
<img class="aspect-square object-cover w-40 rounded-full" src="/avatar.jpg" />
10-2. グラデーション
<h1 class="
text-5xl font-black
bg-gradient-to-r from-pink-500 via-fuchsia-500 to-indigo-500
bg-clip-text text-transparent
">
Beautiful Gradient Text
</h1>
<!-- v4 の conic / radial -->
<div class="size-40 rounded-full bg-conic from-rose-400 via-yellow-400 to-rose-400"></div>
<div class="size-40 bg-radial from-sky-200 to-sky-600"></div>
10-3. backdrop blur(ガラス UI)
<header class="
sticky top-0 z-50
backdrop-blur-md bg-white/70 dark:bg-slate-900/70
border-b border-slate-200/60
px-6 py-3
">
Glassmorphism Header
</header>
11. arbitrary value と任意プロパティ
11-1. 数値を直接指定
<!-- デザインカンプの実寸を取り込む -->
<div class="w-[357px] h-[212px] bg-[#1d4ed8] text-[14.5px] leading-[1.65]">
Pixel-perfect block
</div>
11-2. arbitrary property
<!-- 任意 CSS プロパティ -->
<div class="[mask-image:linear-gradient(black,transparent)]">
下に向かってフェードアウト
</div>
<!-- カスタムプロパティを設定 -->
<div class="[--card-h:280px] h-[var(--card-h)]"></div>
11-3. arbitrary variant
<ul class="list-disc">
<li class="[&:nth-child(odd)]:bg-slate-100 p-2">奇数行に背景</li>
<li class="[&:nth-child(odd)]:bg-slate-100 p-2">偶数行</li>
<li class="[&:nth-child(odd)]:bg-slate-100 p-2">奇数行に背景</li>
</ul>
12. @apply とコンポーネント抽象化
12-1. @apply の基本
同じユーティリティの組み合わせが何度も出てくる場合は @apply で集約できます。ただし 「multi-use コンポーネント」だけに留め、思考停止で増やさない のがコツです。
@layer components {
.btn {
@apply inline-flex items-center justify-center
rounded-lg px-4 py-2 font-medium
transition-colors disabled:opacity-50;
}
.btn-primary {
@apply btn bg-indigo-600 text-white hover:bg-indigo-500;
}
.btn-ghost {
@apply btn text-slate-700 hover:bg-slate-100;
}
}
<button class="btn-primary">Save</button>
<button class="btn-ghost">Cancel</button>
12-2. React で抽象化する場合
v4 + React の現代的なベストプラクティスは、@apply ではなく cva と clsx を使った関数抽象化 です(後述)。
13. shadcn/ui との連携 — モダン React UI のスタンダード
13-1. shadcn/ui とは
shadcn/ui は「npm パッケージとして配布されない、コピペで導入する Radix UI + Tailwind ベースのコンポーネント集」です。Tailwind v4 とは初期化コマンド一発で統合できます。
# 初期化
npx shadcn@latest init
# コンポーネント追加
npx shadcn@latest add button card dialog input form
13-2. 生成される Button(抜粋)
// components/ui/button.tsx
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium " +
"transition-colors focus-visible:outline-none focus-visible:ring-2 " +
"disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-white hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: { variant: "default", size: "default" },
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp ref={ref} className={cn(buttonVariants({ variant, size, className }))} {...props} />
)
}
)
Button.displayName = "Button"
13-3. CSS 変数とのブリッジ
/* app/globals.css */
@import "tailwindcss";
@theme {
--color-primary: oklch(0.55 0.18 260);
--color-primary-foreground: oklch(0.98 0 0);
--color-destructive: oklch(0.55 0.20 25);
--color-background: oklch(1 0 0);
--color-foreground: oklch(0.15 0 0);
--color-accent: oklch(0.96 0.01 250);
--color-accent-foreground: oklch(0.20 0 0);
--color-input: oklch(0.90 0 0);
--radius: 0.5rem;
}
14. cva / clsx / tailwind-merge — クラス管理の三種の神器
14-1. clsx — 条件付きクラス結合
import clsx from "clsx"
function Tag({ active, danger }: { active: boolean; danger?: boolean }) {
return (
<span className={clsx(
"px-2 py-0.5 rounded text-xs font-semibold",
active && "bg-indigo-100 text-indigo-700",
!active && "bg-slate-100 text-slate-600",
danger && "bg-rose-100 text-rose-700",
)}>
Tag
</span>
)
}
14-2. tailwind-merge — クラス衝突を解決
p-4 と p-6 が両方ついた時、あとから書かれた値が必ず勝つ ように仲裁してくれるのが tailwind-merge です。
import { twMerge } from "tailwind-merge"
twMerge("p-4 p-6") // → "p-6"
twMerge("text-sm md:text-base text-lg") // → "md:text-base text-lg"
twMerge("bg-red-500 bg-blue-500") // → "bg-blue-500"
14-3. cn ユーティリティ(プロジェクト標準)
// lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
// 使用例
<div className={cn(
"p-4 bg-white rounded",
isActive && "bg-indigo-50 ring-2 ring-indigo-500",
className, // props として渡された上書きを最後に
)} />
14-4. cva — variant パターンの型安全管理
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const cardVariants = cva(
"rounded-xl border bg-card text-card-foreground shadow-sm",
{
variants: {
tone: {
neutral: "border-slate-200",
info: "border-sky-200 bg-sky-50",
warn: "border-amber-200 bg-amber-50",
danger: "border-rose-200 bg-rose-50",
},
padding: {
sm: "p-3",
md: "p-5",
lg: "p-8",
},
},
defaultVariants: { tone: "neutral", padding: "md" },
}
)
type CardProps = React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof cardVariants>
export function Card({ className, tone, padding, ...props }: CardProps) {
return <div className={cn(cardVariants({ tone, padding }), className)} {...props} />
}
15. プラグインと公式エクステンション
15-1. @tailwindcss/typography
Markdown / MDX のレンダリングで活躍する prose クラス。
npm install -D @tailwindcss/typography
/* CSS 内で読み込む */
@import "tailwindcss";
@plugin "@tailwindcss/typography";
<article class="prose prose-slate dark:prose-invert lg:prose-lg max-w-none">
<h1>Hello</h1>
<p>ブログ本文の見た目が一発で整います</p>
</article>
15-2. @tailwindcss/forms
@plugin "@tailwindcss/forms";
<form class="space-y-4 max-w-sm">
<input type="email" class="block w-full rounded-md border-slate-300 focus:ring-indigo-500 focus:border-indigo-500" />
<select class="block w-full rounded-md border-slate-300">
<option>Free</option><option>Pro</option>
</select>
<label class="flex items-center gap-2">
<input type="checkbox" class="rounded text-indigo-600 focus:ring-indigo-500" />
利用規約に同意する
</label>
</form>
15-3. DaisyUI / Tailwind UI
「コンポーネントをコピペするのも面倒」というケースには、CSS だけで完結する DaisyUI や、公式コマーシャル製品の Tailwind UI という選択肢もあります。
npm install -D daisyui@beta
/* globals.css */
@import "tailwindcss";
@plugin "daisyui";
<button class="btn btn-primary">DaisyUI ボタン</button>
<div class="alert alert-success">保存しました</div>
16. CSS-in-JS との比較 — なぜ Tailwind なのか
16-1. 主要アプローチ比較
方式 ランタイム DX パフォーマンス チーム拡張性
----------------+--------------+---------+----------------+-------------
styled-components あり 良 中(JS実行) △
emotion あり 良 中 △
vanilla-extract なし 良 高 ◯
CSS Modules なし 普通 高 ◯
Tailwind CSS なし とても良 とても高 ◎
16-2. styled-components 風 → Tailwind 移行例
// Before: styled-components
const Card = styled.div`
padding: 1.5rem;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
`
// After: Tailwind v4
function Card({ children }: { children: React.ReactNode }) {
return <div className="p-6 bg-white rounded-xl shadow-md">{children}</div>
}
16-3. Tailwind を選ぶ判断基準
採用すべきとき:
- 中〜大規模プロジェクトで命名規約に時間を取られている
- デザインシステムを CSS 変数で運用したい
- バンドルサイズを徹底的に削りたい
- shadcn/ui 等の現代的 React UI と組みたい
慎重に検討すべきとき:
- HTML が極端に複雑で、可読性を最優先にしたい場合
- 既存の SCSS 資産が膨大で移行コストが見合わない場合
17. パフォーマンス最適化 — JIT と未使用クラス除去
17-1. JIT(Just-In-Time)エンジン
Tailwind v3 で正式採用された JIT は、v4 でさらに高速化しています。「HTML / JSX に書かれたクラスだけ」を CSS として出力 するため、最終的な CSS は通常 5〜20KB(gzip 後)に収まります。
17-2. content の最適化
v4 では @source で対象ファイルを明示できます。
@import "tailwindcss";
@source "./app/**/*.{ts,tsx}";
@source "./components/**/*.{ts,tsx}";
@source "./content/**/*.md";
/* 自動検出から除外したい場合 */
@source not "./legacy/**/*";
17-3. 動的クラスのアンチパターン
// ❌ 動的に文字列結合してクラスを作ると JIT が拾えない
const color = "red"
<div className={`bg-${color}-500`} /> // 出力されない
// ✅ 必ず完全な文字列として記述
const colorClass = {
red: "bg-red-500",
blue: "bg-blue-500",
green: "bg-green-500",
}[color]
<div className={colorClass} />
17-4. CSS バンドル計測
# Vite なら
npx vite-bundle-visualizer
# Next.js なら
ANALYZE=true npm run build
# 一般的な目安(gzip 後)
最小構成: 3〜5 KB
中規模 SPA: 10〜20 KB
大型ダッシュボード: 25〜40 KB
18. パフォーマンス連携 — Web Vitals を悪化させない使い方
18-1. レンダリングブロック対策
Tailwind の CSS は通常 <link rel="stylesheet"> として読み込まれます。Next.js なら App Router の app/layout.tsx 経由で自動的に critical CSS としてインライン化されますが、Vite SPA では下記の最適化を検討します。
<!-- index.html -->
<link rel="preload" href="/assets/index-XXXX.css" as="style" />
<link rel="stylesheet" href="/assets/index-XXXX.css" />
18-2. CLS を抑える — aspect-ratio で領域確保
<!-- 画像が読み込まれる前にも領域を確保し、ガタつき(CLS)を防ぐ -->
<div class="aspect-[16/9] w-full bg-slate-100">
<img src="/hero.webp" class="size-full object-cover" loading="lazy" />
</div>
18-3. LCP 改善 — グラデーション背景の事前読込
<!-- 重要セクションは text-balance + content-visibility で描画コスト削減 -->
<section class="content-visibility-auto contain-intrinsic-size-[600px]">
<h1 class="text-5xl font-black text-balance">
Tailwind v4 で速いサイトを作る
</h1>
</section>
19. 実践: ダッシュボードカードを作る
// components/StatCard.tsx
import { cn } from "@/lib/utils"
import { cva, type VariantProps } from "class-variance-authority"
import { TrendingUp, TrendingDown } from "lucide-react"
const tone = cva("rounded-2xl border p-6 bg-white dark:bg-slate-900", {
variants: {
delta: {
up: "border-emerald-200 dark:border-emerald-900",
down: "border-rose-200 dark:border-rose-900",
flat: "border-slate-200 dark:border-slate-800",
},
},
defaultVariants: { delta: "flat" },
})
interface Props extends VariantProps<typeof tone> {
label: string
value: string
diff?: number
}
export function StatCard({ label, value, diff = 0, delta }: Props) {
const Icon = diff >= 0 ? TrendingUp : TrendingDown
const diffClass = diff >= 0 ? "text-emerald-600" : "text-rose-600"
return (
<div className={cn(tone({ delta }))}>
<p className="text-sm font-medium text-slate-500 dark:text-slate-400">{label}</p>
<div className="mt-2 flex items-baseline justify-between">
<p className="text-3xl font-bold tracking-tight">{value}</p>
<p className={cn("flex items-center gap-1 text-sm font-semibold", diffClass)}>
<Icon className="size-4" /> {Math.abs(diff)}%
</p>
</div>
</div>
)
}
// app/dashboard/page.tsx
<section className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4">
<StatCard label="MRR" value="¥4,820,000" diff={+12} delta="up" />
<StatCard label="新規ユーザー" value="1,284" diff={+3} delta="up" />
<StatCard label="解約率" value="2.1%" diff={-0.3} delta="down" />
<StatCard label="NPS" value="62" diff={0} delta="flat" />
</section>
20. まとめ — Tailwind CSS v4 で得られる開発体験
Tailwind CSS v4 は、単なる「CSS フレームワーク」を超えて、デザインシステム・コンポーネント設計・パフォーマンス最適化を一気通貫で支える基盤 へと進化しました。
本記事で押さえたポイント
- v4 は @import "tailwindcss" だけで導入完了
- @theme で CSS ネイティブにデザイントークン管理
- Lightning CSS により圧倒的なビルド速度
- shadcn/ui + cva + clsx + tailwind-merge が現代の React UI 標準
- container query / 3D 変形 / oklch カラーが標準内蔵
- JIT で最終 CSS は数 KB 〜数十 KB に収まる
- パフォーマンスは aspect-ratio・content-visibility と組み合わせて磨く
「クラス名を考えなくていい」「未使用 CSS が勝手に消える」「デザイナーともデザイントークンで会話できる」——この体験は一度味わうと戻れません。学習コストはアイデアの呼び名(p-4 は padding 1rem など)を覚える数日だけ。今日から手元のプロジェクトに @import "tailwindcss" の 1 行を追加して、Utility-First のスピード感を体感してみてください。
さらに React + Tailwind v4 を本格的に学びたい場合は、現役エンジニアによるマンツーマン指導を受けられるオンラインスクールを活用すると、独学では気づきにくいベストプラクティスを短期間で吸収できます。テックアカデミーのフロントエンド系コースや 侍エンジニアのオーダーメイドカリキュラム、転職保証つきの DMM WEBCAMP、そして即戦力エンジニアを目指せる レバテック系サービスなど、自分の目的に合うものを比較検討してみるとよいでしょう。

コメント