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

Why Dark Mode Is a UX Decision, Not Just a Feature

Dark mode often gets treated as a visual upgrade, but in real-world applications, it directly affects usability, accessibility, and user satisfaction.

Many users actively avoid apps that don’t respect their preferred theme. When dark mode feels inconsistent or resets unexpectedly, it breaks trust — especially in productivity or content-heavy platforms.

That’s why implementing theme switching correctly matters more than it seems.

User Preference vs Developer Preference

One mistake developers make is forcing a theme based on personal taste. In practice:

  • Some users prefer dark mode during night hours
  • Others switch themes based on lighting conditions
  • Accessibility users may rely on higher contrast

A good implementation respects user choice first, then system preference, and only then default styling.

📱 Real-World Use Cases Where Dark Mode Matters Most

Dark mode has a noticeable impact in applications like:

  • Developer dashboards and admin panels
  • Reading platforms and blogs
  • E-commerce apps used late at night
  • Mobile-first apps with long session times

In these cases, poor theme handling leads to eye fatigue and faster drop-offs.

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.

Subscribe to our Newsletter

Provide your email to get email notification when we launch new products or publish new articles

email

Share with your friends: