How to Add Dark Mode in Next.js 15 App Router with Tailwind CSS V4 (Latest Method)
Learn how to implement dark and light mode in your Next.js 15 App Router project using Tailwind CSS V4. This step-by-step guide covers the latest changes in Tailwind V4 and Next.js 15 for a seamless theme switcher!
If you already have a Next.js project, skip to the Step 1 section.
Step 1: Create a New Next.js Project
To install Next.js 15 with the App Router and the latest Tailwind CSS v4, follow these steps:
- 1.Create a project folder anywhere on your drive.
- 2.Open the folder in VS Code.
- 3.Open the terminal and run the following command:
For npm users:
npx create-next-app@latest .
For pnpm users:
pnpx create-next-app@latest .
During installation, you’ll be prompted with options:
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? @/*
Use the arrow keys to select options and press Enter. Make sure to enable Tailwind CSS and App Router.
By default, Next.js installs Tailwind CSS v3
. To upgrade, follow the next section.
Step 2: Upgrade to Tailwind CSS v4
To upgrade Tailwind CSS to v4
, run the following command in your terminal:
For npm users:
npx @tailwindcss/upgrade
For pnpm users:
pnpx @tailwindcss/upgrade
If you encounter errors, try forcing the upgrade:
npx @tailwindcss/upgrade --force
pnpx @tailwindcss/upgrade --force
This will automatically update your PostCSS configuration and replace outdated Tailwind CSS classes.
For more details, check the official
Upgrading your Tailwind CSS projects from v3 to v4.

Step 3: Manually Install Tailwind CSS v4 ( If Upgrade Fails )
If the upgrade fails, manually install Tailwind CSS v4 by running:
npm install tailwindcss @tailwindcss/postcss postcss
pnpm install tailwindcss @tailwindcss/postcss postcss
For pnpm:
Update Your postcss.config.mjs or postcss.config.js
Replace the contents with:
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
'@tailwindcss/postcss': {},
},
};
export default config;
Modify Your Global CSS File
Remove the old Tailwind imports:
old global.css
@tailwind base;
@tailwind components;
@tailwind utilities;
And replace them with
@import "tailwindcss";
For more details, visit the
Setting up Tailwind CSS in a Next.js project.

Step 4: Enable Dark Mode in Tailwind CSS v4
Why is the dark
class not working?
In Tailwind CSS v4, the tailwind.config.js
file has been removed, and the darkMode: "class" setting no longer exists. This means you need to enable dark mode directly in your global CSS file.
Solution:
Add the following to your global CSS file:
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
This will detect the .dark class and apply the correct dark mode styles.
Step 5: Add a Dark Mode Toggle Button
Install next-themes
To easily toggle dark mode, install the next-themes
package:
An abstraction for themes in your React app.. Latest version: 0.4.4, last published: 3 months ago. Start using next-themes in your project by running `npm i next-themes`. There are 956 other projects in the npm registry using next-themes.


For npm:
npm install next-themes
For pnpm:
pnpm install next-themes
Create a Theme Provider Component
- 1.Inside your src folder, create a new folder called
theme
. - 2.Inside
theme
, create a file namedtheme-provider.jsx
(or.tsx
for TypeScript). - 3.Add the following code:
For .jsx
files:
"use client";
import { ThemeProvider as NextThemesProvider } from "next-themes";
export default function ThemeProvider({ children, ...props }) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}
For .tsx
files ( If you are using TypeScript ):
"use client";
import {
ThemeProvider as NextThemesProvider,
ThemeProviderProps,
} from "next-themes";
export default function ThemeProvider({
children,
...props
}: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}
Add Theme Provider in the Root Layout
Open your layout.jsx
or layout.tsx
file (usually located in the root folder), and modify it as follows:
For .jsx
files:
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>
);
}
For .tsx
files ( If you are using 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>
);
}
Make sure to import the ThemeProvider
component from the one we created, not from next-theme
.
Make sure to add the suppressHydrationWarning
attribute inside the <html>
tag to prevent React hydration errors when switching themes.
Theme Provider props Explanation:
defaultTheme="system"
: Sets the default theme to match the system theme.enableSystem
: Allows automatic switching between dark and light modes based on the user's system preferences.disableTransitionOnChange
: Disables all CSS transitions when switching themes to ensure a flicker-free experience.
Create a Dark & Light Mode Toggle Button
- 1.Inside the
theme
folder, createtheme-toggle.jsx
(or.tsx
for TypeScript). - 2.Add the following code:
"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>
);
}
Add the Toggle Button to Your Navbar
Integrate the ThemeToggle
component into your navigation bar or any desired location:
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>
);
}
Update the home page
In your home page paste this code
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>
);
}
Common Ways to Add Dark Mode Styles
There are two common ways to apply dark mode styles in Tailwind CSS:
1. Using dark:
Classes
Tailwind CSS provides the dark:
variant, which allows you to apply styles when dark mode is active.
For example:
<body className="bg-white dark:bg-[#191919] text-[#37352f] dark:text-[#ffffffcf]">
Here, the bg-white
, dark:bg-[#191919]
, text-[#37352f]
, and dark:text-[#ffffffcf]
classes ensure the background and text colors adapt automatically when dark mode is enabled.
2. Using CSS Variables
Instead of adding dark:
classes everywhere, you can define theme-specific variables in global.css
and use them across your styles.
For example:
@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);
}
}
We extended Tailwind’s color system by defining custom properties for shadows, text, background, and border colors using CSS variables. These variables are set in :root
for light mode and updated inside .dark
for dark mode.
Now, we can hover-background
bg-active-background
button-border-color
use them in our components like this:
<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]"
>
Since the variable values change based on the theme, the styles update automatically without needing dark:
classes everywhere.
Updated Files for Dark Mode in 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
Step 6: Start Your Development Server and Test It
For npm users:
npm run dev
For pnpm users:
pnpm dev
Visit your localhost and toggle between dark and light modes using the button. Ensure the theme persists across pages and correctly follows the system theme on the first load.
Congratulations! You've successfully implemented a seamless dark and light mode toggle in Next.js 15 and Tailwind CSS v4.
Check out my other blogs as well!
How to Safelist Classes in Tailwind CSS V4

How to Safelist Classes in Tailwind CSS V4
Learn how to safelist classes in Tailwind CSS v4 after the removal of the tailwind.config.js file. Discover the new method to manually define safelisted classes and ensure they are applied correctly in your project
How to Highlight Code Syntax with PrismJS Statically in Next.js with Dark and Light Themes

How to Highlight Code Syntax with PrismJS Statically in Next.js with Dark and Light Themes
Learn how to add static code block syntax highlighting using PrismJS in Next.js, with support for both dark and light themes.
How to add google analytics in Next.js 15 app router ( simple method )

How to add google analytics in Next.js 15 app router ( simple method )
Learn how to easily integrate Google Analytics into your Next.js 15 app router using the official Next.js component. Follow this step-by-step guide to set up tracking for your website in minutes.