Next.js 15 App Router で Tailwind CSS V4 を使用してダークモードを追加する方法
概要
Next.js 15 App Router で Tailwind CSS V4 を使用してダークモードを追加する方法です。
以下の記事が参考になりました。
以下のように、ダークモードとライトモードを切り替えることができました。
上記の記事について、ChatGPTによる日本語記事です。
Next.js 15 App RouterプロジェクトでTailwind CSS V4を使用してダークモードとライトモードを実装する方法を学びましょう。このステップバイステップガイドでは、シームレスなテーマスイッチャーを実現するためのTailwind V4とNext.js 15の最新の変更点をカバーしています!
既にNext.jsプロジェクトをお持ちの場合は、ステップ1に進んでください。
ステップ1:新しいNext.jsプロジェクトを作成する
App RouterとTailwind CSS v4を搭載したNext.js 15をインストールするには、次の手順に従ってください:
- 任意の場所にプロジェクトフォルダを作成します。
- VS Codeでフォルダを開きます。
- ターミナルを開き、次のコマンドを実行します:
npmユーザーの場合:
npx create-next-app@latest .
pnpmユーザーの場合:
pnpx create-next-app@latest .
インストール中に、次のオプションが表示されます:
What is your project named? my-app
Would you like to use TypeScript? No / Yes
Would you like to use ESLint? No / Yes
Would you like to use Tailwind CSS? No / Yes
Would you like your code inside a `src/` directory? No / Yes
Would you like to use App Router? (recommended) No / Yes
Would you like to use Turbopack for `next dev`? No / Yes
Would you like to customize the import alias (`@/*` by default)? No / Yes
What import alias would you like configured? @/*
矢印キーでオプションを選択し、Enterキーを押します。Tailwind CSSとApp Routerを必ず有効にしてください。
デフォルトでは、Next.jsはTailwind CSS v3をインストールします。アップグレードするには、次のセクションに進んでください。
ステップ2:Tailwind CSS v4にアップグレードする
Tailwind CSSをv4にアップグレードするには、ターミナルで次のコマンドを実行します:
npmユーザーの場合:
npx @tailwindcss/upgrade
pnpmユーザーの場合:
pnpx @tailwindcss/upgrade
エラーが発生した場合は、強制アップグレードを試してください:
npx @tailwindcss/upgrade --force
pnpx @tailwindcss/upgrade --force
これにより、PostCSS設定が自動的に更新され、古いTailwind CSSクラスが置き換えられます。
詳細については、公式アップグレードガイドをチェックしてください。
ステップ3:Tailwind CSS v4を手動でインストールする(アップグレードが失敗した場合)
アップグレードが失敗した場合は、次のコマンドを実行してTailwind CSS v4を手動でインストールします:
npm install tailwindcss @tailwindcss/postcss postcss
pnpmの場合:
pnpm install tailwindcss @tailwindcss/postcss postcss
postcss.config.mjsまたはpostcss.config.jsを更新する
内容を次のように置き換えます:
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
'@tailwindcss/postcss': {},
},
};
export default config;
グローバルCSSファイルを修正する
古いTailwindのインポートを削除します:
@tailwind base;
@tailwind components;
@tailwind utilities;
そして、次のように置き換えます:
@import "tailwindcss";
詳細については、Next.jsでTailwind CSSをインストールする方法を参照してください。
ステップ4:Tailwind CSS v4でダークモードを有効にする
なぜdarkクラスが機能しないのか?
Tailwind CSS v4では、tailwind.config.jsファイルが削除され、darkMode: "class"
設定が存在しなくなりました。つまり、グローバルCSSファイルで直接ダークモードを有効にする必要があります。
解決策:
グローバルCSSファイルに次を追加します:
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
これにより、.darkクラスが検出され、正しいダークモードのスタイルが適用されます。
ステップ5:ダークモード切り替えボタンを追加する
next-themesをインストールする
ダークモードを簡単に切り替えるには、next-themesパッケージをインストールします:
npmの場合:
npm install next-themes
pnpmの場合:
pnpm install next-themes
テーマプロバイダーコンポーネントを作成する
- srcフォルダ内にthemeという新しいフォルダを作成します。
- themeフォルダ内に、theme-provider.jsx(またはTypeScriptの場合は.tsx)という名前のファイルを作成します。
- 以下のコードを追加します:
.jsxファイルの場合:
"use client";
import { ThemeProvider as NextThemesProvider } from "next-themes";
export default function ThemeProvider({ children, ...props }) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}
.tsxファイルの場合(TypeScriptを使用している場合):
"use client";
import {
ThemeProvider as NextThemesProvider,
ThemeProviderProps,
} from "next-themes";
export default function ThemeProvider({
children,
...props
}: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}
ルートレイアウトにテーマプロバイダーを追加する
layout.jsxまたはlayout.tsxファイル(通常はルートフォルダにあります)を開き、次のように修正します:
.jsxファイルの場合:
import Navbar from "../components/navbar.jsx";
import ThemeProvider from "../theme/theme-provider";
import "./globals.css";
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<body className="bg-white dark:bg-[#191919] text-[#37352f] dark:text-[#ffffffcf]">
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<Navbar />
{children}
</ThemeProvider>
</body>
</html>
);
}
.tsxファイルの場合(TypeScriptを使用している場合):
import Navbar from "../components/navbar.jsx";
import ThemeProvider from "../theme/theme-provider";
import "./globals.css";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body className="bg-white dark:bg-[#191919] text-[#37352f] dark:text-[#ffffffcf]">
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<Navbar />
{children}
</ThemeProvider>
</body>
</html>
);
}
⚠️ 作成したThemeProviderコンポーネントをインポートし、next-themeからではないことを確認してください。
⚠️ テーマを切り替える際のReactハイドレーションエラーを防ぐために、<html>
タグの中にsuppressHydrationWarning属性を追加することを忘れないでください。
テーマプロバイダープロップの説明:
-
defaultTheme="system"
:デフォルトテーマをシステムテーマに合わせます。 -
enableSystem
:ユーザーのシステム設定に基づいてダークモードとライトモードの自動切り替えを許可します。 -
disableTransitionOnChange
:テーマ切り替え時にちらつきのない体験を確保するために、すべてのCSSトランジションを無効にします。
ダークモードとライトモード切り替えボタンを作成する
- themeフォルダ内にtheme-toggle.jsx(またはTypeScriptの場合は.tsx)を作成します。
- 以下のコードを追加します:
"use client";
import { useTheme } from "next-themes";
export default function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<button
type="button"
className="cursor-pointer bg-white dark:bg-[#191919] text-[#37352f] dark:text-[#ffffffcf] hover:bg-hover-background active:bg-active-background rounded-md border border-button-border-color p-1.5 [transition:background_20ms_ease-in,_color_0.15s]"
title="Toggle theme"
aria-label="Toggle theme"
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
>
<svg
role="graphics-symbol"
viewBox="0 0 15 15"
width="15"
height="15"
fill="none"
className="w-4 h-4 dark:hidden"
>
<path
d="M7.5 0C7.77614 0 8 0.223858 8 0.5V2.5C8 2.77614 7.77614 3 7.5 3C7.22386 3 7 2.77614 7 2.5V0.5C7 0.223858 7.22386 0 7.5 0ZM2.1967 2.1967C2.39196 2.00144 2.70854 2.00144 2.90381 2.1967L4.31802 3.61091C4.51328 3.80617 4.51328 4.12276 4.31802 4.31802C4.12276 4.51328 3.80617 4.51328 3.61091 4.31802L2.1967 2.90381C2.00144 2.70854 2.00144 2.39196 2.1967 2.1967ZM0.5 7C0.223858 7 0 7.22386 0 7.5C0 7.77614 0.223858 8 0.5 8H2.5C2.77614 8 3 7.77614 3 7.5C3 7.22386 2.77614 7 2.5 7H0.5ZM2.1967 12.8033C2.00144 12.608 2.00144 12.2915 2.1967 12.0962L3.61091 10.682C3.80617 10.4867 4.12276 10.4867 4.31802 10.682C4.51328 10.8772 4.51328 11.1938 4.31802 11.3891L2.90381 12.8033C2.70854 12.9986 2.39196 12.9986 2.1967 12.8033ZM12.5 7C12.2239 7 12 7.22386 12 7.5C12 7.77614 12.2239 8 12.5 8H14.5C14.7761 8 15 7.77614 15 7.5C15 7.22386 14.7761 7 14.5 7H12.5ZM10.682 4.31802C10.4867 4.12276 10.4867 3.80617 10.682 3.61091L12.0962 2.1967C12.2915 2.00144 12.608 2.00144 12.8033 2.1967C12.9986 2.39196 12.9986 2.70854 12.8033 2.90381L11.3891 4.31802C11.1938 4.51328 10.8772 4.51328 10.682 4.31802ZM8 12.5C8 12.2239 7.77614 12 7.5 12C7.22386 12 7 12.2239 7 12.5V14.5C7 14.7761 7.22386 15 7.5 15C7.77614 15 8 14.7761 8 14.5V12.5ZM10.682 10.682C10.8772 10.4867 11.1938 10.4867 11.3891 10.682L12.8033 12.0962C12.9986 12.2915 12.9986 12.608 12.8033 12.8033C12.608 12.9986 12.2915 12.9986 12.0962 12.8033L10.682 11.3891C10.4867 11.1938 10.4867 10.8772 10.682 10.682ZM5.5 7.5C5.5 6.39543 6.39543 5.5 7.5 5.5C8.60457 5.5 9.5 6.39543 9.5 7.5C9.5 8.60457 8.60457 9.5 7.5 9.5C6.39543 9.5 5.5 8.60457 5.5 7.5ZM7.5 4.5C5.84315 4.5 4.5 5.84315 4.5 7.5C4.5 9.15685 5.84315 10.5 7.5 10.5C9.15685 10.5 10.5 9.15685 10.5 7.5C10.5 5.84315 9.15685 4.5 7.5 4.5Z"
fill="currentColor"
fillRule="evenodd"
clipRule="evenodd"
></path>
</svg>
<svg
role="graphics-symbol"
viewBox="0 0 15 15"
width="15"
height="15"
fill="none"
className="hidden w-4 h-4 dark:block"
>
<path
d="M2.89998 0.499976C2.89998 0.279062 2.72089 0.0999756 2.49998 0.0999756C2.27906 0.0999756 2.09998 0.279062 2.09998 0.499976V1.09998H1.49998C1.27906 1.09998 1.09998 1.27906 1.09998 1.49998C1.09998 1.72089 1.27906 1.89998 1.49998 1.89998H2.09998V2.49998C2.09998 2.72089 2.27906 2.89998 2.49998 2.89998C2.72089 2.89998 2.89998 2.72089 2.89998 2.49998V1.89998H3.49998C3.72089 1.89998 3.89998 1.72089 3.89998 1.49998C3.89998 1.27906 3.72089 1.09998 3.49998 1.09998H2.89998V0.499976ZM5.89998 3.49998C5.89998 3.27906 5.72089 3.09998 5.49998 3.09998C5.27906 3.09998 5.09998 3.27906 5.09998 3.49998V4.09998H4.49998C4.27906 4.09998 4.09998 4.27906 4.09998 4.49998C4.09998 4.72089 4.27906 4.89998 4.49998 4.89998H5.09998V5.49998C5.09998 5.72089 5.27906 5.89998 5.49998 5.89998C5.72089 5.89998 5.89998 5.72089 5.89998 5.49998V4.89998H6.49998C6.72089 4.89998 6.89998 4.72089 6.89998 4.49998C6.89998 4.27906 6.72089 4.09998 6.49998 4.09998H5.89998V3.49998ZM1.89998 6.49998C1.89998 6.27906 1.72089 6.09998 1.49998 6.09998C1.27906 6.09998 1.09998 6.27906 1.09998 6.49998V7.09998H0.499976C0.279062 7.09998 0.0999756 7.27906 0.0999756 7.49998C0.0999756 7.72089 0.279062 7.89998 0.499976 7.89998H1.09998V8.49998C1.09998 8.72089 1.27906 8.89997 1.49998 8.89997C1.72089 8.89997 1.89998 8.72089 1.89998 8.49998V7.89998H2.49998C2.72089 7.89998 2.89998 7.72089 2.89998 7.49998C2.89998 7.27906 2.72089 7.09998 2.49998 7.09998H1.89998V6.49998ZM8.54406 0.98184L8.24618 0.941586C8.03275 0.917676 7.90692 1.1655 8.02936 1.34194C8.17013 1.54479 8.29981 1.75592 8.41754 1.97445C8.91878 2.90485 9.20322 3.96932 9.20322 5.10022C9.20322 8.37201 6.82247 11.0878 3.69887 11.6097C3.45736 11.65 3.20988 11.6772 2.96008 11.6906C2.74563 11.702 2.62729 11.9535 2.77721 12.1072C2.84551 12.1773 2.91535 12.2458 2.98667 12.3128L3.05883 12.3795L3.31883 12.6045L3.50684 12.7532L3.62796 12.8433L3.81491 12.9742L3.99079 13.089C4.11175 13.1651 4.23536 13.2375 4.36157 13.3059L4.62496 13.4412L4.88553 13.5607L5.18837 13.6828L5.43169 13.7686C5.56564 13.8128 5.70149 13.8529 5.83857 13.8885C5.94262 13.9155 6.04767 13.9401 6.15405 13.9622C6.27993 13.9883 6.40713 14.0109 6.53544 14.0298L6.85241 14.0685L7.11934 14.0892C7.24637 14.0965 7.37436 14.1002 7.50322 14.1002C11.1483 14.1002 14.1032 11.1453 14.1032 7.50023C14.1032 7.25044 14.0893 7.00389 14.0623 6.76131L14.0255 6.48407C13.991 6.26083 13.9453 6.04129 13.8891 5.82642C13.8213 5.56709 13.7382 5.31398 13.6409 5.06881L13.5279 4.80132L13.4507 4.63542L13.3766 4.48666C13.2178 4.17773 13.0353 3.88295 12.8312 3.60423L12.6782 3.40352L12.4793 3.16432L12.3157 2.98361L12.1961 2.85951L12.0355 2.70246L11.8134 2.50184L11.4925 2.24191L11.2483 2.06498L10.9562 1.87446L10.6346 1.68894L10.3073 1.52378L10.1938 1.47176L9.95488 1.3706L9.67791 1.2669L9.42566 1.1846L9.10075 1.09489L8.83599 1.03486L8.54406 0.98184ZM10.4032 5.30023C10.4032 4.27588 10.2002 3.29829 9.83244 2.40604C11.7623 3.28995 13.1032 5.23862 13.1032 7.50023C13.1032 10.593 10.596 13.1002 7.50322 13.1002C6.63646 13.1002 5.81597 12.9036 5.08355 12.5522C6.5419 12.0941 7.81081 11.2082 8.74322 10.0416C8.87963 10.2284 9.10028 10.3497 9.34928 10.3497C9.76349 10.3497 10.0993 10.0139 10.0993 9.59971C10.0993 9.24256 9.84965 8.94373 9.51535 8.86816C9.57741 8.75165 9.63653 8.63334 9.6926 8.51332C9.88358 8.63163 10.1088 8.69993 10.35 8.69993C11.0403 8.69993 11.6 8.14028 11.6 7.44993C11.6 6.75976 11.0406 6.20024 10.3505 6.19993C10.3853 5.90487 10.4032 5.60464 10.4032 5.30023Z"
fill="currentColor"
fillRule="evenodd"
clipRule="evenodd"
></path>
</svg>
</button>
);
}
ナビゲーションバーに切り替えボタンを追加する
ThemeToggleコンポーネントをナビゲーションバーまたは任意の場所に統合します:
import ThemeToggle from "../theme/theme-toggle";
export default function Navbar() {
return (
<header className="bg-white dark:bg-[#191919] text-[#37352f] dark:text-[#ffffffcf] shadow-box-shadow-first sticky top-0 z-10 h-11 w-full">
<div className="mx-auto flex h-full max-w-7xl justify-between items-center px-4">
<h1 className="text-md font-bold">My Website</h1>
<ThemeToggle />
</div>
</header>
);
}
ホームページを更新する
ホームページに以下のコードを貼り付けます:
export default function Page() {
return (
<main className="flex flex-col items-center text-center justify-center w-full min-w-0 min-h-[calc(100dvh-2.75rem)] mx-auto max-w-7xl">
<h1 className="text-4xl font-extrabold mb-2">♥️ Hii</h1>
</main>
);
}
ダークモードスタイルを追加する一般的な方法
Tailwind CSSでダークモードスタイルを適用する一般的な方法は2つあります:
1. dark:クラスを使用する
Tailwind CSSは、ダークモードがアクティブなときにスタイルを適用できるdark:バリアントを提供します。
例えば:
<body className="bg-white dark:bg-[#191919] text-[#37352f] dark:text-[#ffffffcf]">
ここで、bg-white、dark:bg-[#191919]、text-[#37352f]、dark:text-[#ffffffcf]クラスは、ダークモードが有効になると背景色とテキスト色が自動的に適応することを保証します。
2. CSS変数を使用する
すべての場所にdark:クラスを追加する代わりに、global.cssでテーマ固有の変数を定義し、スタイル全体で使用できます。
例えば:
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
@theme {
--shadow-box-shadow-first: 0px 1px 0px var(--shadow-color);
--color-button-border-color: var(--border-color);
--color-hover-background: var(--hover-background);
--color-active-background: var(--active-background);
}
@layer base {
:root {
--shadow-color: rgba(55, 53, 47, 0.09);
--border-color: rgba(55, 53, 47, 0.09);
--hover-background: rgba(55, 53, 47, 0.06);
--active-background: rgba(55, 53, 47, 0.16);
}
.dark {
--shadow-color: rgba(255, 255, 255, 0.094);
--border-color: rgba(255, 255, 255, 0.094);
--hover-background: rgba(255, 255, 255, 0.055);
--active-background: rgba(255, 255, 255, 0.03);
}
}
CSS変数を使用して、影、テキスト、背景、ボーダーカラーのカスタムプロパティを定義することで、Tailwindのカラーシステムを拡張しました。これらの変数は、ライトモード用に:rootで設定され、ダークモード用に.dark内で更新されます。
これで、次のようにコンポーネントで使用できます:
<button
type="button"
className="cursor-pointer hover:bg-hover-background active:bg-active-background rounded-md border border-button-border-color p-1.5 [transition:background_20ms_ease-in,_color_0.15s]"
>
変数の値はテーマに基づいて変化するため、すべての場所にdark:クラスを必要とせずに、スタイルは自動的に更新されます。
Next.jsのダークモード用の更新済みファイル
nextjs-app/
│── src/
│ ├── app/
│ │ ├── layout.jsx
│ │ ├── page.jsx
│ │ ├── globals.css
│ ├── components/
│ │ ├── Navbar.jsx
│ ├── theme/
│ │ ├── ThemeToggle.jsx
│ │ ├── ThemeProvider.jsx
│── package.json
│── postcss.config.js
ステップ6:開発サーバーを起動してテストする
npmユーザーの場合:
npm run dev
pnpmユーザーの場合:
pnpm dev
ローカルホストにアクセスし、ボタンを使用してダークモードとライトモードを切り替えます。テーマがページ間で保持され、最初の読み込み時にシステムテーマを正しく追従することを確認します。
🎉 おめでとうございます!Next.js 15とTailwind CSS v4でシームレスなダークモードとライトモードの切り替えを実装することに成功しました。
Discussion