Light & Dark Mode in Next.js

May 1, 2025

Install next-themes

pnpm i next-themes

For app router:

  • Wrap children with ThemeProvider in app/layout.tsx
  • Pass attribute="data-theme" prop to ThemeProvider
import { ThemeProvider } from "next-themes";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode,
}>) {
  return (
    <html lang="en" suppressHydrationWarning>
      <head>
        <meta charSet="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      </head>
      <body>
        <ThemeProvider attribute="data-theme">{children}</ThemeProvider>
      </body>
    </html>
  );
}

  • Note: If you do not add suppressHydrationWarning to your <html> you will get warnings because next-themes updates that element. This property only applies one level deep, so it won't block hydration warnings on other elements.

Create a ThemeSwitch component for one-click theme switching

import { useTheme } from "next-themes";

const ThemeSwitch = () => {
  const { resolvedTheme, setTheme } = useTheme();

  const toggleTheme = (theme: string | undefined) => {
    if (theme === "light") {
      setTheme("dark");
    } else {
      setTheme("light");
    }
  };

  return (
    <div>
      <button
        className="cursor-pointer text-2xl"
        onClick={()=> toggleTheme(resolvedTheme)}
      >
        {resolvedTheme === "dark" ? "🌙" : "☀️"}
      </button>
    </div>
  );
};

export default ThemeSwitch;

Use ThemeSwitch

"use client";
import Link from "next/link";
import dynamic from "next/dynamic";

const ThemeSwitch = dynamic(() => import("./ThemeSwitch"), {
  ssr: false,
  loading: () => <p>...</p>,
});

export const Header = () => {
  return (
    <header className="border-b border-dashed border-black dark:border-white flex justify-between items-center p-5">
      <div>
        <Link href="/">
          <h1 className="text-2xl">The Cosmo Nomad</h1>
        </Link>
      </div>
      <ThemeSwitch />
    </header>
  );
};

For Tailwind 4.1 and above, add this to global CSS:

@import "tailwindcss";

@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));

Use the dark: prefix for Tailwind utility classes to apply them in dark mode

className = "border-b border-dashed border-black dark:border-white";

References