Light & Dark Mode in Next.js apps

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