← Back to Blog
Dark ModeTailwindNext.js

Implementing Dark Mode in Next.js with Tailwind CSS

18 May 2026 · by Yunmin Shin

Why Dark Mode Is Worth Implementing

Dark mode reduces eye strain in low-light environments, extends battery life on OLED screens (common on mid-range Android devices popular in Thailand), and is expected as a standard feature by many users in 2026. Apps and websites that lack dark mode feel dated.

The implementation in Next.js with Tailwind CSS is straightforward if you follow the right pattern from the start. Getting it wrong leads to the classic problem: a flash of the wrong theme on page load.

How Does Tailwind's Dark Mode Work?

Tailwind v4 supports dark mode via the dark: variant. Any class prefixed with dark: applies only when dark mode is active:

<div class="bg-white text-gray-900 dark:bg-gray-950 dark:text-gray-100">
  Content here
</div>

Tailwind supports two dark mode strategies:

  1. media strategy — Responds to the operating system's prefers-color-scheme: dark media query. Automatic, requires no JavaScript, but gives the user no control.
  2. class strategy — Applies dark mode when the dark class is present on the <html> element. Allows manual toggling but requires JavaScript.

For most applications, use the class strategy so users can override their system preference.

How Do You Implement a Theme Toggle?

Create a ThemeProvider client component that manages the theme state:

"use client";
import { createContext, useContext, useEffect, useState } from "react";

type Theme = "light" | "dark" | "system";

// Apply theme to <html> element
function applyTheme(theme: Theme) {
  const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
  const isDark = theme === "dark" || (theme === "system" && prefersDark);
  document.documentElement.classList.toggle("dark", isDark);
}

Store the user's preference in localStorage:

useEffect(() => {
  const saved = localStorage.getItem("theme") as Theme ?? "system";
  setTheme(saved);
  applyTheme(saved);
}, []);

How Do You Prevent the Flash of Wrong Theme?

The "flash" problem occurs because React hydrates on the client after the server has already rendered the HTML. If the server renders light mode HTML and the user prefers dark mode, there is a brief flash of light before JavaScript kicks in and switches to dark.

The solution is a blocking inline script in the <head> that runs before the page renders:

<script dangerouslySetInnerHTML={{ __html: `
  (function() {
    var theme = localStorage.getItem('theme') || 'system';
    var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
    if (theme === 'dark' || (theme === 'system' && prefersDark)) {
      document.documentElement.classList.add('dark');
    }
  })();
` }} />

Add this script to your root layout.tsx inside the <head>. It executes synchronously before the browser renders any content, preventing the flash entirely.

What About SSR and Hydration Mismatches?

Server-rendered HTML cannot know the user's localStorage value. This can cause React hydration mismatches if you render different content based on theme server-side. The safest approach: render all content server-side without theme-dependent differences, and let Tailwind's CSS handle the visual switch. Only diverge based on theme in purely visual (CSS) ways, not in structural (JSX) ways.

Ready to Build Something Fast?

Get a free quote on LINE. We reply within 24 hours.

Ready to build something fast and scalable?

Get a free project quote on LINE. We reply within 24 hours.

무료 견적 on LINE