Tailwind CSS v4 へのマイグレーションでやったこと
Tailwind CSS v3 -> v4 へのマイグレーション(移行)を行いました。
躓きポイントもあったので、メモです。
🏗️ 対象プロジェクト
こちら:
私の個人サイトのプロジェクトです。
フロントエンドの主な技術スタック
- TypeScript 5
- React 19
- React Router 7
- shadcn/ui
という構成です。
パッケージマネージャーとして pnpm を使っています。
✅ 差分全量
関係ないものも混在していますが、こんな感じです。
🛠️ やったこと
🤔 一旦 Dependabot の PR をそのままビルド
一旦ビルドしてみて、エラー内容を確認します。
pnpm build
↓
[vite:css] [postcss] It looks like you're trying to use `tailwindcss` directly as a PostCSS plugin. The PostCSS plugin has moved to a separate package, so to continue using Tailwind CSS with PostCSS you'll need to install `@tailwindcss/postcss` and update your PostCSS configuration.
マイグレーション作業が必要です、と。
❌️ とりあえず公式のマイグレーションツールを使ってみる
npx @tailwindcss/upgrade
を実行しろと書いてあるので、やってみます。
するとエラーに。
Error: Cannot apply unknown utility class `border-border`. Are you using CSS modules or similar and missing `@reference`? https://tailwindcss.com/docs/functions-and-directives#reference-directive
@reference が欠けてるんじゃないかということですが、それが原因なわけないので敢えて無視しました。
というわけで、公式に従って手動でマイグレーションしていくことにします。
🧹 PostCSS の設定を削除する
依存関係から postcss を削除します。
pnpm remove postcss
設定ファイル postcss.config.js も削除します。
⚙️ Vite プラグイン設定を追加する
依存関係に Vite プラグインを追加します。
pnpm add -D @tailwindcss/vite
Vite の設定を追加します。
+ import tailwindcss from "@tailwindcss/vite";
// https://vite.dev/config/
export default defineConfig({
- plugins: [react()],
+ plugins: [react(), tailwindcss()],
@tailwindcss/oxide と esbuild が postinstall を要求してくるので許可します。
pnpm approve-builds
これでもう一回インストール。
pnpm install
成功です。
🔧 調整
@tailwind ディレクティブを削除し、新たな設定を追加します。
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
+ @import "tailwindcss";
+ @config "./../tailwind.config.js";
v4 では tailwind.config.ts は基本使わないようになりますが、明示して読み込むこともできます。
一旦動かしたいので @config "./../tailwind.config.js"; を書いています。
※あとで削除します。
🪶 tailwind.config.js を廃止する
v4 の目玉?の1つである CSS-first configuration へ移行します。
元々こんな感じのファイルがありました。
/** @type {import('tailwindcss').Config} */
export default {
darkMode: ["class"],
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
colors: {
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
chart: {
1: "hsl(var(--chart-1))",
2: "hsl(var(--chart-2))",
3: "hsl(var(--chart-3))",
4: "hsl(var(--chart-4))",
5: "hsl(var(--chart-5))",
},
},
},
},
plugins: [],
};
-
darkMode: ["class"]→ CSS に移行src/index.css@custom-variant dark (&:where(.dark, .dark *)); -
content: ...→ 廃止 -
theme: ...→ CSS に移行src/index.css@theme { --border-radius-lg: var(--radius); --border-radius-md: calc(var(--radius) - 2px); --border-radius-sm: calc(var(--radius) - 4px); --color-background: hsl(var(--background)); --color-foreground: hsl(var(--foreground)); --color-card: hsl(var(--card)); --color-card-foreground: hsl(var(--card-foreground)); --color-popover: hsl(var(--popover)); --color-popover-foreground: hsl(var(--popover-foreground)); --color-primary: hsl(var(--primary)); --color-primary-foreground: hsl(var(--primary-foreground)); --color-secondary: hsl(var(--secondary)); --color-secondary-foreground: hsl(var(--secondary-foreground)); --color-muted: hsl(var(--muted)); --color-muted-foreground: hsl(var(--muted-foreground)); --color-accent: hsl(var(--accent)); --color-accent-foreground: hsl(var(--accent-foreground)); --color-destructive: hsl(var(--destructive)); --color-destructive-foreground: hsl(var(--destructive-foreground)); --color-border: hsl(var(--border)); --color-input: hsl(var(--input)); --color-ring: hsl(var(--ring)); --color-chart-1: hsl(var(--chart-1)); --color-chart-2: hsl(var(--chart-2)); --color-chart-3: hsl(var(--chart-3)); --color-chart-4: hsl(var(--chart-4)); --color-chart-5: hsl(var(--chart-5)); }
このままだと border-border 等のクラスが見つからず、エラーになります。
Cannot apply unknown utility class `border-border`
このように変更してあげます。
@layer base {
* {
- @apply border-border;
+ border-color: hsl(var(--border));
}
body {
- @apply bg-background text-foreground;
+ background-color: hsl(var(--background));
+ color: hsl(var(--foreground));
}
}
…以上!
📚 参考
- Upgrade guide - Getting started - Tailwind CSS
- Tailwind CSS v4.0 - Tailwind CSS
- Dark mode - Core concepts - Tailwind CSS
- Tailwind CSS V4まとめ!
- [v4] Cannot apply unknown utility class · tailwindlabs/tailwindcss · Discussion #13336
- [bug]: Build Error With Tailwind CSS Prefix
The 'border-border' class does not exist.· Issue #6066 · shadcn-ui/ui - css - The
border-borderclass does not exist. Ifborder-borderis a custom class, make sure it is defined within a@layerdirective - Stack Overflow - Tailwind CSS v4 で導入される CSS First Configurations
- Next.js 15 App Router で Tailwind CSS V4 を使用してダークモードを追加する方法
Discussion