storieasy-logo

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

milan

Milan Patel

25 May 2025

|

25 min to read

dark-light-mode-tailwindcss

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.

Newsletter

Subscribe to our newsletter and get our latest updates.

Share with your friends: