Light and Dark Mode in Tailwind CSS v4 with Next.js

DarkMode
LightMode
ThemeToggle
TailwindCSS
NextJS
DarkLightMode
ModernWebDesign
Learn how to implement a modern and responsive dark/light mode toggle in your Next.js 14 app using Tailwind CSS v4 β with and without localStorage. Fully explained with code and best practices.
π§ Why Dark Mode?
Dark mode isnβt just a trend it improves user experience by reducing eye strain and saving battery on OLED devices. Tailwind CSS v4 makes it easier and more customizable than ever.
In this blog, youβll learn two complete approaches to toggling between light and dark themes:
- β Without using localStorage (just user system preference)
- β With localStorage (persistent across sessions)
π§±1. Setup Next.js App
1npx create-next-app@latest dark-light-mode-app
π¨ 2. Add Global Styles (CSS Variables for Themes)
Inside your app/globals.css, define your custom theme using CSS variables:
1@import "tailwindcss";
2
3@custom-variant dark (&:where(.dark, .dark *));
4
5@theme inline {
6 --color-bg: var(--bg);
7 --color-fg: var(--fg);
8}
9
10:root {
11 --bg: #ffffff;
12 --fg: #000000;
13}
14
15.dark {
16 --bg: #0a0a0a;
17 --fg: #ffffff;
18}
19
20body {
21 background: var(--color-bg);
22 color: var(--color-fg);
23 transition: background 0.3s, color 0.3s;
24}
25
3. Create a Layout (app/layout.tsx)
Ensure that theme styles are applied correctly on load.
1import "./globals.css";
2
3export default function RootLayout({
4 children,
5}: {
6 children: React.ReactNode;
7}) {
8 return (
9 <html lang="en" suppressHydrationWarning>
10 <body className="bg-background text-foreground transition-colors duration-300">
11 {children}
12 </body>
13 </html>
14 );
15}
16
π 4. Create the Theme Toggle Button (Without localStorage)
Create app/components/ThemeToggle.tsx:
1'use client'
2
3import { useEffect, useState } from 'react'
4import { Sun, Moon } from 'lucide-react'
5
6export default function ThemeToggle() {
7 const [isDark, setIsDark] = useState(false)
8
9 useEffect(() => {
10 const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
11 document.documentElement.classList.toggle('dark', prefersDark)
12 setIsDark(prefersDark)
13 }, [])
14
15 const toggleTheme = () => {
16 const newDark = !isDark
17 document.documentElement.classList.toggle('dark', newDark)
18 setIsDark(newDark)
19 }
20
21 return (
22 <button
23 onClick={toggleTheme}
24 className="w-10 h-10 rounded-full flex items-center justify-center
25 bg-yellow-300 dark:bg-gray-800 transition-colors"
26 >
27 {isDark ? <Moon className="text-white w-5 h-5" /> : <Sun className="text-black w-5 h-5" />}
28 </button>
29 )
30}
31
β This version relies on the userβs system theme and toggles class without saving preference.
πΎ 5. (Optional) Theme Toggle with localStorage
To remember the theme between reloads, use this version of the toggle:
1'use client'
2
3import { useEffect, useState } from 'react'
4import { Sun, Moon } from 'lucide-react'
5
6export default function ThemeToggle() {
7 const [isDark, setIsDark] = useState(false)
8
9 useEffect(() => {
10 const stored = localStorage.getItem('theme')
11 const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
12 const shouldUseDark = stored === 'dark' || (!stored && prefersDark)
13
14 setIsDark(shouldUseDark)
15 document.documentElement.classList.toggle('dark', shouldUseDark)
16 }, [])
17
18 const toggleTheme = () => {
19 const newDark = !isDark
20 setIsDark(newDark)
21 document.documentElement.classList.toggle('dark', newDark)
22 localStorage.setItem('theme', newDark ? 'dark' : 'light')
23 }
24
25 return (
26 <button
27 onClick={toggleTheme}
28 className="w-10 h-10 flex items-center justify-center rounded-full bg-yellow-300 dark:bg-gray-700"
29 >
30 {isDark ? <Moon className="w-5 h-5 text-white" /> : <Sun className="w-5 h-5 text-black" />}
31 </button>
32 )
33}
34
β This version checks and stores the theme preference in localStorage.
π‘ 6. Add It to Your Page (app/page.tsx)
1import ThemeToggle from "./components/ThemeToggle";
2
3export default function Home() {
4 return (
5 <div className="bg-background text-foreground min-h-screen p-6">
6 <ThemeToggle />
7 <h1 className="text-2xl font-bold mt-6">
8 Light/Dark Mode with Tailwind CSS v4
9 </h1>
10 </div>
11 );
12}
13
π§ Pro Tips
- Use prefers-color-scheme only on first load, then rely on localStorage (if used).
- Add a darkMode toggle in the app settings or nav menu for better UX.
- Test on real devices for system preference detection.