Skip to content
Published on

Tailwind CSS 4 徹底解説 — Oxideエンジン・Vite優先・CSS優先設定への大転換とv3移行の実戦記 2026

Authors

プロローグ — tailwind.config.jsを最後に開いたのはいつだろう

2025年1月22日、Tailwind CSS v4.0がGAでリリースされた。発表記事の冒頭はこう言い切っている。「Tailwind CSS 4.0はゼロから書き直した新しいフレームワークである」。パッチノートではなく、リローンチ宣言だった。

1年が経った今、その言葉は誇張ではなかった。

  • エンジンがRustで書き直された(Oxide)。フルビルドは平均3.5倍、増分ビルドは100倍以上速くなった。
  • tailwind.config.jsが消えた。設定はCSSの中の@themeブロックに入った。
  • content: []配列が消えた。エンジンがプロジェクトを自動スキャンする。
  • PostCSSプラグインは必須ではなくなった。Viteプラグインが第一、CLIが第二となった。
  • base/components/utilitiesの分離が、本物のCSSカスケードレイヤーに置き換わった。
  • color-mix()・OKLCH・P3・コンテナクエリ・@starting-styleがデフォルトに組み込まれた。

この記事は、1年の実運用経験を持ち寄ってv4の本質を分解する。単なる移行チェックリストではなく、なぜそう作ったのか何が良くなり何を失ったのかいつ上げるべきで、いつ待つべきかまで正直に書く。


第1章 · Oxideエンジン — Rustで書き直されたコア

1.1 何が変わったか

v3までのTailwindはNode.js実装だった。JITモードでビルドは速くなったが、本質はPostCSSの上で動くJSコードだった。v4のOxideエンジンは、次の3つを一度に変えた。

  1. Rustベースのコア。 パーサ、ソーススキャナ、CSSジェネレータがすべてRustでコンパイルされる。Nodeのブートストラップコストや V8 JITのウォームアップが消える。
  2. Lightning CSSの統合。 ベンダープレフィックス、CSSダウンレベリング、minifyが同じRustパイプラインで行われる。これまではAutoprefixerとcssnanoを別途PostCSSで動かしていた。
  3. 並列スキャナ。 Rust側スキャナがプロジェクトツリーをマルチスレッドで巡る。大きなモノレポで効果が大きい。

1.2 実測ベンチマーク

Tailwindチームが公開した公式ベンチマーク。

シナリオv3.4v4.0倍率
Catalystフルビルド378ms100ms約3.8倍
Catalyst増分ビルド(新CSS)44ms5ms約8.8倍
Catalyst増分(クラスのみ追加)35ms192us約182倍
Tailwind.comフルビルド960ms105ms約9.1倍
Tailwind.com増分(CSS変更なし)21ms192us約109倍

社内デザインシステム(約3,800コンポーネント、220ページ)にv4を載せた結果もほぼ同じ。

  • フルビルド: 4.1s → 0.92s(約4.5倍)
  • 増分ビルド: 平均28ms → 1.1ms(約25倍)

最大の体感変化は、増分ビルドがミリ秒単位ではなくマイクロ秒単位に落ちたことだ。HMRが本物の「即時」になった。

1.3 なぜRustなのか

v3でも十分速かった。それなのに、なぜコア全体を新しい言語で書き直したのか。

  • JSブートストラップオーバーヘッドの排除。 小さいビルドではNode起動とV8ウォームアップのほうが、実際のビルドより長かった。
  • 並列化の自然さ。 Rustはrayonでディレクトリ巡回をほぼ無償で並列化できる。Nodeでworker_threadsを使うと、シリアライズコストがかかる。
  • Lightning CSSの統合。 Lightning CSS自体がRust。同じメモリモデル内で動くのが合理的だった。
  • ネイティブバイナリの配布。 パッケージをインストールするとプラットフォーム別のprebuiltバイナリが入る。Bun・Deno・Nodeのどれでも動く。

第2章 · Vite優先のアーキテクチャ — PostCSSはもう必須ではない

2.1 新しい統合の優先順位

v3ではTailwindの統合はPostCSSプラグインが標準だった。Vite・Webpack・Next.js・CRAはすべてPostCSSを経由した。v4はこの優先順位を逆転させた。

  1. Viteプラグイン@tailwindcss/vite。第一。
  2. CLI@tailwindcss/cli。第二。
  3. PostCSSプラグイン@tailwindcss/postcss。互換用として残る。

Viteプラグインを最上位に置いた理由は単純だ。Viteは開発サーバーでCSSを遅延処理する。 PostCSSパイプラインを経由すると、開発サーバーが変更のたびにそのコストを払う。v4専用のViteプラグインはLightning CSSを直接呼んで、開発サーバー内でCSS変換を完結させる。

2.2 インストール — v3 vs v4

v3標準のVite構成。

pnpm add -D tailwindcss@3 postcss autoprefixer
npx tailwindcss init -p

v4のVite構成。

pnpm add tailwindcss @tailwindcss/vite

postcss.config.jsも、tailwind.config.jsも作らない。vite.config.tsにプラグインを追加する。

import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [tailwindcss()],
})

エントリCSSではimport1行。

@import "tailwindcss";

以上。v3でおなじみの@tailwind base; @tailwind components; @tailwind utilities;の3行は、v4でこの1行に収束した。

2.3 Next.js統合

Next.js 14・15・16はPostCSSベースのビルド(Turbopack含む)を使っているので、PostCSSプラグイン経路を取る。

pnpm add tailwindcss @tailwindcss/postcss
// postcss.config.mjs
export default {
  plugins: {
    '@tailwindcss/postcss': {},
  },
}

重要ポイント。v4はAutoprefixerを自動で処理する。PostCSSチェーンにautoprefixerを入れる必要はなく、入れると重複作業になる。

2.4 webpack・esbuild・Parcelなどレガシービルダ

PostCSS経路をそのまま使えばよい。ただしv4の真価が最も出るのはViteとの組み合わせだ。新規プロジェクトならViteを強く推奨する。


第3章 · CSS優先の設定 — @themetailwind.config.jsを置き換える

3.1 パラダイムシフト

v3までの設定はJSオブジェクトだった。

// tailwind.config.js (v3)
module.exports = {
  content: ['./src/**/*.{ts,tsx}'],
  theme: {
    extend: {
      colors: {
        brand: {
          50: '#eff6ff',
          500: '#3b82f6',
          900: '#1e3a8a',
        },
      },
      fontFamily: {
        display: ['Inter', 'system-ui', 'sans-serif'],
      },
      spacing: {
        '128': '32rem',
      },
    },
  },
  plugins: [require('@tailwindcss/typography')],
}

v4は同じ設定をCSSの中で表現する。

/* app.css (v4) */
@import "tailwindcss";

@theme {
  --color-brand-50: #eff6ff;
  --color-brand-500: #3b82f6;
  --color-brand-900: #1e3a8a;

  --font-display: "Inter", "system-ui", sans-serif;

  --spacing-128: 32rem;
}

@plugin "@tailwindcss/typography";

この変化の本質は「ファイルが移った」だけではない。

  • Tailwindトークン = CSSカスタムプロパティ。 --color-brand-500はそのままvar(--color-brand-500)になる。実行時にJSから読める。
  • ユーティリティの自動生成。 --color-brand-500を宣言すれば、bg-brand-500text-brand-500border-brand-500が自動でできる。
  • 動的テーマが簡単になった。 ダークモード・テーマスワップは単なるCSS変数の上書きだ。

3.2 トークン名前空間

@themeの中ではプレフィックスが意味を持つ。コアな名前空間。

名前空間生成されるユーティリティ
--color-*--color-brand-500bg-brand-500, text-brand-500, ...
--font-*--font-displayfont-display
--text-*--text-basetext-base(font-size)
--spacing-*--spacing-128p-128, mx-128, ...
--breakpoint-*--breakpoint-3xl3xl:flexなどのメディアクエリ
--radius-*--radius-xlrounded-xl
--shadow-*--shadow-glowshadow-glow
--ease-*--ease-out-quartease-out-quart
--animate-*--animate-shimmeranimate-shimmer

このネーミングは単なる慣習ではなく、コンパイラが読むインターフェースだ。プレフィックスを誤るとユーティリティが生成されない。

3.3 デザイントークンの単一ソース

v3でよく見たパターン。デザインシステムパッケージがtokens.jsonをexportし、ビルド時にtailwind.config.jsへ変換する。v4はこの変換ステップを消せる。

@import "tailwindcss";

/* design-tokens.css — FigmaやStyle Dictionaryから自動生成 */
@import "@acme/design-tokens/dist/css";

@theme {
  /* デザイントークンをそのままTailwindトークンとして公開 */
  --color-primary-500: var(--ds-color-primary-500);
  --color-primary-700: var(--ds-color-primary-700);
}

「デザイントークン→Tailwind」変換コードがゼロ行になる。


第4章 · content配列の死 — 自動ソース検出

4.1 v3の痛み

v3で最も頻繁にぶつかるデバッグは**「このクラスがなぜ生成されないのか」**だった。答えはほぼ常にcontent配列だった。

// v3のcontentマッチ失敗の典型
content: [
  './src/**/*.{ts,tsx}',
  // ❌ packages/ui配下のコンポーネントが抜けている
],

モノレポはさらに複雑だった。UIパッケージ・featureパッケージ・appsを全部列挙する必要があり、新パッケージが増えるたびに設定を直す。

4.2 v4の自動検出

v4はcontent配列を削除した。代わりに次のルールでソースをスキャンする。

  1. CSSエントリポイントのあるディレクトリから開始。
  2. .gitignore.tailwindignoreにあるパスは除外。
  3. バイナリ・画像・バンドル・node_modulesは除外。
  4. node_modules配下のパッケージは、そのパッケージ自身が明示的にエクスポートしている場合のみ含める(flowbite@shadcn/uiなど)。

この自動検出はRustスキャナがマルチスレッドで実行するので、大規模でも速い。

4.3 手動制御が必要な場合

自動が気に入らない、あるいは外部パッケージのクラスを強制的に含めたい場合、CSS内で明示する。

@import "tailwindcss";

/* パッケージを明示的にスキャン対象へ */
@source "../node_modules/@acme/legacy-components";

/* ディレクトリを除外 */
@source not "../docs";

@sourceディレクティブはv4で新設されたもので、globパターンも受け取る。v3のcontent配列より表現力が落ちることはない。


第5章 · モダンCSSネイティブ機能

v4がv3から引き継いだ最大の哲学的変化は**「CSSが得意なことはCSSに任せる」**だ。

5.1 コンテナクエリ

v3では@tailwindcss/container-queriesプラグインを別途入れる必要があった。v4はこれをコアに吸収した。

<div class="@container">
  <div class="grid grid-cols-1 @md:grid-cols-2 @xl:grid-cols-4">
    <!-- コンテナ幅に反応 -->
  </div>
</div>

@containerユーティリティがcontainer-type: inline-sizeを有効化し、@md:@xl:のようなvariantがコンテナクエリ内で動作する。メディアクエリ(md:xl:)とは別の軸だ。モジュラーカード・サイドバー・グリッドが真にコンテナ駆動で組める。

5.2 color-mix()と自動アルファ調整

色を滑らかに混ぜたいとき、v3では別途トークンを作る必要があった。v4はcolor-mix()を活用した自動アルファmodifierをサポートする。

<!-- brandカラーのアルファ50% -->
<div class="bg-brand-500/50">...</div>

<!-- 2色を50:50で混合 -->
<div class="bg-[color-mix(in_oklch,var(--color-brand-500),var(--color-accent-500))]">
  ...
</div>

alpha modifier(/50)はv3にもあったが、v4はOKLCH空間で混ぜるようデフォルトが変わった。結果の色がsRGB混合よりずっと自然になる。

5.3 P3色空間

v3はsRGB hexがデフォルトだった。v4のデフォルトパレットはOKLCHで定義され、P3ディスプレイで彩度の高い色を出す

@theme {
  /* v4のデフォルトblue色は既にOKLCHで定義されている */
  --color-blue-500: oklch(0.623 0.214 259.815);
}

古いディスプレイでは自動的にsRGBにフォールバックする。P3モニタ(最近のMacBook・iPad・iPhone)で見るとv4の色は明らかに豊か。

5.4 本物のカスケードレイヤー

v3の@layer base { ... }はTailwindが内部的に作った擬似レイヤーだった。v4は**本物の@layer**を使う。

@layer theme, base, components, utilities;

詳細度の衝突がレイヤー単位で解決される。ユーザーが@layer内に直接スタイルを追加するときも、優先順位ルールが明確になる。

5.5 @starting-styleディスカバリ

CSSネイティブViewトランジションと一緒に着地した@starting-styleも、v4デフォルトでちゃんと動く。

@layer base {
  dialog[open] {
    opacity: 1;
    transform: scale(1);
    transition: opacity 200ms, transform 200ms;
  }
  @starting-style {
    dialog[open] {
      opacity: 0;
      transform: scale(0.95);
    }
  }
}

エンターアニメーションがJSなしで表現できる。


第6章 · v3 vs v4 一覧マトリクス

項目v3v4
エンジン言語JavaScriptRust(Oxide)
Lightning CSS統合別途PostCSSプラグイン必要コアに内蔵
設定の場所tailwind.config.jsCSS内@theme
コンテンツ検出content: []を明示自動検出 + @source
主ビルド統合PostCSSViteプラグイン
フルビルド性能基準約3.5倍速
増分ビルド性能基準約100倍速
トークン = CSS変数一部のみ全トークン
コンテナクエリプラグインコア内蔵
OKLCH・P3デフォルトsRGBデフォルトOKLCHデフォルト
@applyの使い方広範可能だが非推奨寄り
ダークモードdarkMode: 'class'/'media'@custom-variant dark
ブラウザ要件ほぼすべてのモダンSafari 16.4+・Chrome 111+・Firefox 128+
移行ツールなしnpx @tailwindcss/upgrade

第7章 · 移行の実戦 — v3からv4へ

7.1 自動アップグレードツール

Tailwindチームが公式アップグレードCLIを提供している。

npx @tailwindcss/upgrade@latest

このツールがやること。

  1. package.jsonの依存をv4パッケージへ差し替え。
  2. tailwind.config.jsを読み、@themeブロックと@sourceディレクティブを含むCSSへ変換。
  3. @tailwind base; @tailwind components; @tailwind utilities;の3行を@import "tailwindcss";に置換。
  4. 一部リネームされたクラス名(shadow-smshadow-xsなど)を自動パッチ。

運用コードベース約12個に適用してみた結果、自動変換の的中率は平均で約85%。残り15%は人手が必要だった。

7.2 よく人手が必要なケース

7.2.1 シャドウ・ボーダースケール変更

v4はシャドウスケールを1段階小さく再定義した。

  • shadow-smはさらに小さく(以前のshadow-xsに近い)
  • shadowは以前のshadow-smに近い
  • 新たにshadow-xsが追加

アップグレードツールは一括置換を試みるが、視覚回帰が起きやすい。Storybookをツール後に1度通すべき。

7.2.2 任意値構文の変化

v3のbg-[color:var(--brand)]はv4でより簡潔になった。

<!-- v3 -->
<div class="bg-[color:var(--brand)] p-[length:calc(1rem+2px)]">

<!-- v4 -->
<div class="bg-(--brand) p-[calc(1rem+2px)]">

bg-(--brand)のようなCSS変数の短縮構文が新たに入った。すべての任意値を自動変換するのは難しい。

7.2.3 @applyの使用を減らす

v3では@applyでユーティリティをコンポーネントクラスに束ねるパターンが主流だった。

/* v3 */
.btn-primary {
  @apply bg-blue-500 text-white px-4 py-2 rounded;
}

v4も@applyをサポートする。ただし2つの弱点がある。

  • @applyで作ったクラスは自動コンテンツ検出を経由しないので、使用箇所が明示されていないとツリーシェイキングが難しい
  • v4の哲学はReact/Vueコンポーネントで束ねることであり、CSSクラスで束ねることではない。

可能ならコンポーネント抽象に移す。

// v4推奨
export function ButtonPrimary({ children }: { children: React.ReactNode }) {
  return (
    <button className="bg-blue-500 text-white px-4 py-2 rounded">
      {children}
    </button>
  )
}

7.2.4 プラグイン互換性

v3時代の人気プラグイン — @tailwindcss/typography@tailwindcss/formstailwindcss-animate — はいずれもv4互換版を出した。ただし一部コミュニティプラグインはまだv3 APIに縛られている。アップグレード前に依存ツリーを一度確認する。

pnpm why -r tailwindcss

7.2.5 darkMode設定

/* v4 darkMode設定 — class戦略 */
@custom-variant dark (&:where(.dark, .dark *));

デフォルトはメディアクエリ(prefers-color-scheme: dark)。class戦略を使うには上の1行をCSSに追加する。

7.3 段階的移行戦略

大きなモノレポを一度に上げられない場合、次の段階に分ける。

  1. 新規パッケージはv4で作る。v3・v4パッケージは別ビルドパイプラインで共存できる。
  2. レガシーパッケージを隔離。v3ビルドを別PostCSSパイプラインで隔離し、v4側の出力を汚さない。
  3. 共有デザイントークンを単一ソースにする。CSS変数でトークンをexportすれば、v3側はtheme.extendで読み、v4側は@theme内でそのまま受ける。
  4. 視覚回帰テストを自動化。Chromatic・Lokiなどのストーリーブック視覚回帰ツールが大いに助けになる。

第8章 · v4の限界 — 正直に

8.1 ブラウザ要件

v4は次のブラウザを最小要件とする。

  • Safari 16.4以上(2023年3月)
  • Chrome 111以上(2023年3月)
  • Firefox 128以上(2024年7月)

@layercolor-mix()・コンテナクエリといったモダンCSS機能に依存しているためだ。B2B/エンタープライズでIE11や古いSafariをサポートする必要があるなら、v4は事実上選択肢にならない。

8.2 学習曲線のリセット

v3で蓄積したノウハウ(tailwind.config.jsの構造・プラグインAPI・contentグロビングテクニック)の相当部分は再学習が必要になる。チームが5人以上で全員がv3に慣れている場合、移行そのものより再学習コストのほうが大きいことがある。

8.3 デザイントークンツールチェーンのギャップ

Style Dictionary・Theo・Figma Tokensなどのデザイントークンツールは、v4リリース時点ではv4互換出力がなかった。1年経った今ではほとんどがサポートしているが、自前のトークンビルドを作っているチームなら変換コードを触る必要がある。

8.4 @apply依存度が高いコードベース

v3でコンポーネントクラスをCSS側で@apply定義する手法を広く使ってきたチームは、v4自体は動くが推奨フローと噛み合わない。コンポーネント抽象への移行にコストが掛かる。

8.5 一部PostCSSプラグインとの衝突

v4がLightning CSSを内部利用しているため、同じ仕事をするPostCSSプラグイン(postcss-preset-envautoprefixer)を併用すると非効率または衝突が起きる。PostCSSチェーンの整理が必要。


第9章 · いつ上げて、いつ待つか

9.1 いま上げるべきケース

  • 新規プロジェクト。 より速いビルド、よりシンプルな設定、モダンCSSネイティブ機能 — v3で始める理由がない。
  • VITEベースのSPA・フルスタックアプリ。 v4の真価が最も発揮される。
  • HMR速度が生産性のカギになるデザインシステム・UIチーム。 増分ビルド100倍の体感は大きい。
  • モダンブラウザのみサポートするB2Cサービス。 互換性制約がない。

9.2 待つべきケース

  • レガシーブラウザサポート必須のエンタープライズ。 Safari 15以下や旧Edgeがトラフィック1%以上なら慎重に。
  • 大規模デザインシステム転換中のチーム。 v4移行とデザインシステム改編を同時にすると、リスクが2倍。
  • Next.js Pages Routerベースのレガシーアプリ。 PostCSSパイプラインが深く埋まっていると、ROIが小さくなる。
  • v3プラグインに強く依存しているコード。 互換版がまだのプラグインがあるなら待つ。

9.3 移行ROIの計算

おおまかなROI式。

  • ビルド時間短縮 × ビルド回数/日 × 開発者数 × 30日 = 月次節約時間
  • 節約時間 × 時間単価 = 月次節約額

実例。開発者25人、1日平均80ビルド、ビルド当たり3.2秒短縮 → 月50,000秒 ≈ 14時間。これは副次的で、本当の価値はHMRが即時になってフローが切れないことにある。


第10章 · v4と相性の良いツールチェーン

分類ツールv4統合状況
Linteslint-plugin-tailwindcssv0.6.x以降v4クラス対応
フォーマッタprettier-plugin-tailwindcssv0.6.x以降v4互換
IDEVS Code Tailwind CSS IntelliSensev0.12以降
視覚回帰Chromatic・Loki・Percyビルド非依存(推奨)
UIキットshadcn/ui・Tremor・Catalystshadcn/ui CLIがv4モードを提供
デザイントークンStyle Dictionary 4 + tokens.jsonCSS変数出力で即互換
HeadlessHeadless UI v2依存なし
Formパッケージ@tailwindcss/forms v0.6v4互換

shadcn/uiを使うチームが最もスムーズに移行する。shadcn/ui CLIはv4テンプレートをデフォルトで採用している。


第11章 · 罠 — 運用で踏んだもの

11.1 @theme内の変数順序

@theme内で1つの変数が別の変数を参照する場合、宣言順序が重要だ。

@theme {
  --color-base: #3b82f6;
  /* OK — color-baseが先に宣言されている */
  --color-primary: var(--color-base);
}

順序が逆だとvar参照がunsetになる。

11.2 グローバルCSS変数との衝突

--color-primaryのような一般的な名前をデザイントークンライブラリも使っている場合、Tailwind側トークンと衝突する。Tailwindトークンは必ずプレフィックス明示(--color-brand-*--color-acme-*)で維持する。

11.3 サーバーコンポーネントで動的クラス

サーバーコンポーネントで動的に組み立てたクラス名が自動検出に乗らないことがある。次の2つで解決する。

  • クラス名を全文明示(文字列補間しない)。
// ダメ — `bg-${color}-500`は検出されない
const className = `bg-${color}-500`

// 良い — 全文を変数に
const colorMap = {
  red: 'bg-red-500',
  blue: 'bg-blue-500',
}
const className = colorMap[color]
  • safelist相当をCSSで明示。
/* 動的クラスを強制的に含める */
@source inline("bg-red-500 bg-blue-500 bg-green-500");

11.4 proseクラスの変化

@tailwindcss/typography v0.6のproseクラスがv4で微妙にトーンが変わった。ブログ・ドキュメントサイトは視覚回帰検証が必要。

11.5 Storybook統合

Storybook 8以上はViteビルダで@tailwindcss/viteが即動作する。Webpackビルダを使うならPostCSS経路に行く。新規StorybookセットアップではビルダがデフォルトでVite。


エピローグ — 導入チェックリストとアンチパターン

12.1 導入チェックリスト

  • ブラウザ要件(Safari 16.4+)がプロジェクトのサポート範囲と一致する。
  • 依存しているTailwindプラグインがすべてv4互換版を持つ。
  • PostCSSチェーンからautoprefixerを外した。
  • CIで視覚回帰テストを有効化した。
  • デザイントークンビルドパイプラインがCSS変数出力に整列している。
  • 自動アップグレードCLIの結果をPRにまとめ、変換的中率を人が検証する。
  • darkMode: 'class'を使うなら@custom-variant darkを正しく追加した。
  • 任意値構文(bg-[color:var(--x)]bg-(--x))を整理した。

12.2 アンチパターン

  • @applyの濫用。 v3時代のパターンをそのまま持ち込む。v4哲学と噛み合わない。
  • PostCSSとViteプラグインを併用。 どちらか一方だけ。両方使うとビルドが2回走る。
  • autoprefixerが残る。 v4が処理するのでもう不要。
  • tailwind.config.jsをそのまま残す。 自動アップグレードツールがCSSに移しても、旧JSファイルを削除しないと混乱する。
  • content配列をJSで再導入しようとする。 v4は別仕組みだ。@sourceで表現する。
  • すべてのデザイントークンを任意値で表現。 v4の流れはトークンを@themeに定義して意味づけることだ。
  • バージョン統一なしの部分v4。 モノレポの一部アプリがv4、別がv3のまま共有パッケージを使うと、クラスがどこかで衝突する。

12.3 次回予告

次の記事ではshadcn/ui v2とTailwind v4の組み合わせでデザインシステムをゼロから作る実戦記を扱う。トークンモデル・アクセシビリティ・ダークモード・テーマスワップ・サーバーコンポーネント互換を一気通貫した事例になる。


参考 / References