🎨

Tailwind CSS v4 へのマイグレーションでやったこと

に公開

Tailwind CSS v3 -> v4 へのマイグレーション(移行)を行いました。
躓きポイントもあったので、メモです。

🏗️ 対象プロジェクト

こちら:

https://github.com/23prime/23prime.xyz

私の個人サイトのプロジェクトです。

フロントエンドの主な技術スタック

  • TypeScript 5
  • React 19
  • React Router 7
  • shadcn/ui

という構成です。

パッケージマネージャーとして pnpm を使っています。

✅ 差分全量

関係ないものも混在していますが、こんな感じです。

https://github.com/23prime/23prime.xyz/pull/4/files

🛠️ やったこと

🤔 一旦 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.

マイグレーション作業が必要です、と。

❌️ とりあえず公式のマイグレーションツールを使ってみる

https://tailwindcss.com/docs/upgrade-guide

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 の設定を追加します。

vite.config.ts
+ import tailwindcss from "@tailwindcss/vite";

  // https://vite.dev/config/
  export default defineConfig({
-   plugins: [react()],
+   plugins: [react(), tailwindcss()],

@tailwindcss/oxideesbuild が postinstall を要求してくるので許可します。

pnpm approve-builds

これでもう一回インストール。

pnpm install

成功です。

🔧 調整

@tailwind ディレクティブを削除し、新たな設定を追加します。

src/index.css
- @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 へ移行します。

元々こんな感じのファイルがありました。

tailwind.config.js
/** @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`

このように変更してあげます。

src/index.css
  @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));
    }
  }

…以上!

📚 参考

Discussion