Next.js TailwindCSS Dark Mode Tutorial

Learn how to create a dark mode, light mode, and system mode toggle with Next.js and TailwindCSS.

Step 1: Create Next.js App

npx create-next-app@latest

Step 2: Clean up

/app/globals.css

@tailwind base;
@tailwind components;
@tailwind utilities;

/app/page.tsx

export default function Home() {
  return <div>Home</div>;
}

Step 3: Update Tailwind Config

/tailwind.config.ts

import type { Config } from "tailwindcss";

const config: Config = {
  // ...
  darkMode: "class",
};
export default config;

Step 4: Add dark mode script to root layout

import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <head>
        <script
          dangerouslySetInnerHTML={{
            __html: `
              if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
                document.documentElement.classList.add('dark')
              } else {
                document.documentElement.classList.remove('dark')
              }
        `,
          }}
        />
      </head>
      <body className={inter.className}>{children}</body>
    </html>
  );
}

Note: Using Next.js' Script component will cause flash of unstyled content (FOUC). Instead, use a regular script element with dangerouslySetInnerHTML.

Step 5: Create a theme switch component

/components/theme-switch.tsx

"use client";

import { useEffect, useState } from "react";

type Theme = "dark" | "light" | "system" | null;

export default function ThemeSwitch() {
  const [theme, setTheme] = useState<Theme>(null);

  useEffect(() => {
    if (localStorage.getItem("theme") === "dark") {
      setTheme("dark");
    } else if (localStorage.getItem("theme") === "light") {
      setTheme("light");
    } else {
      setTheme("system");
    }
  }, []);

  function toggleDarkMode() {
    if (theme === "system") {
      setTheme("dark");
      localStorage.setItem("theme", "dark");
      document.documentElement.classList.add("dark");
    } else if (theme === "dark") {
      setTheme("light");
      localStorage.setItem("theme", "light");
      document.documentElement.classList.remove("dark");
    } else if (theme === "light") {
      setTheme("system");
      localStorage.removeItem("theme");
      applySystemTheme();
    }
  }

  function applySystemTheme() {
    if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
      document.documentElement.classList.add("dark");
    } else {
      document.documentElement.classList.remove("dark");
    }
  }

  return (
    <div>
      <button className="m-5" onClick={toggleDarkMode}>
        {theme === "system" ? "🖥️" : null}
        {theme === "dark" ? "🌙" : null}
        {theme === "light" ? "☀️" : null}
      </button>
    </div>
  );
}

Step 6: Use the theme switch component

import ThemeSwitch from "@/components/theme-switch";

export default function Home() {
  return (
    <div className="text-black bg-white dark:text-white dark:bg-black h-screen">
      <h1>Home</h1>
      <p>Hello, World!</p>
      <ThemeSwitch />
    </div>
  );
}

Reference