DEV Community

fadow
fadow

Posted on

Next.js Dark Mode with Tailwind CSS v4 in 5 Minutes

Dark mode isn't a "nice to have" anymore — users expect it. Here's the fastest way to add it to a Next.js 16 app with Tailwind v4 and next-themes.

1. Install

npm install next-themes
Enter fullscreen mode Exit fullscreen mode

2. Wrap your app

// app/layout.tsx
import { ThemeProvider } from 'next-themes';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

Key flags:

  • attribute="class" — toggles .dark class on <html>, Tailwind picks it up
  • defaultTheme="system" — respects OS preference on first visit
  • suppressHydrationWarning — prevents React hydration mismatch flash

3. Configure Tailwind v4

Tailwind v4 uses CSS-based config. Add this to globals.css:

@import "tailwindcss";

@custom-variant dark (&:where(.dark, .dark *));
Enter fullscreen mode Exit fullscreen mode

That's it. No tailwind.config.js. No darkMode: 'class'. Tailwind v4 just works.

4. Build the toggle

// components/ThemeToggle.tsx
'use client';

import { useTheme } from 'next-themes';
import { useEffect, useState } from 'react';

export function ThemeToggle() {
  const { theme, setTheme } = useTheme();
  const [mounted, setMounted] = useState(false);

  useEffect(() => setMounted(true), []);

  if (!mounted) return <div className="w-9 h-9" />;

  return (
    <button
      onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
      className="p-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800"
    >
      {theme === 'dark' ? '☀️' : '🌙'}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Why the mounted check? next-themes doesn't know the theme until after hydration (it reads from localStorage). Rendering before mount causes a flash of wrong theme.

5. Use in components

<div className="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
  <h1>Hello</h1>
</div>
Enter fullscreen mode Exit fullscreen mode

Every Tailwind utility works with dark: prefix. No special components needed.

Bonus: System theme + manual override

defaultTheme="system" means:

  • First visit → matches OS setting
  • User clicks toggle → saves preference to localStorage
  • Next visit → uses saved preference, ignores OS

This is what users expect. Don't force dark mode. Let them choose.


Full setup (dark mode + i18n + auth + dashboard): Next.js Vietnam Starter Kit — $15, ready in 5 minutes.

Top comments (0)