Tailwind CSS Dark Mode: Complete Setup Guide 2026
Dark mode is now a user expectation. Tailwind CSS makes implementing dark mode straightforward with its built-in dark: prefix. This guide covers both configuration strategies, toggling with React/Next.js, and converting components to support dark mode.
Dark Mode Strategies: Class vs Media
Tailwind CSS supports two dark mode strategies. You configure this in tailwind.config.ts:
Class Strategy (recommended)
darkMode: 'class'Dark mode activates when the dark class is on the <html> element. You control it with JavaScript.
- ✓ User can manually toggle light/dark
- ✓ Can persist preference in localStorage
- ✓ Can default to system preference + allow override
Media Strategy
darkMode: 'media'Dark mode follows the OS setting (prefers-color-scheme: dark) automatically. No JavaScript needed.
- ✓ Zero JavaScript required
- ✗ No manual toggle possible
- ✗ Users can't override OS setting in-app
Recommendation: Use class strategy. It gives you full control and lets users override their OS setting — which is the behavior most apps (VS Code, GitHub, Notion) provide.
Setting Up Tailwind CSS Dark Mode
In Tailwind CSS v3, add darkMode to your config:
// tailwind.config.ts (v3)
import type { Config } from "tailwindcss";
export default {
darkMode: "class",
content: ["./src/**/*.{ts,tsx}"],
theme: { extend: {} },
plugins: [],
} satisfies Config;In Tailwind CSS v4, configure dark mode in your CSS file:
/* globals.css (v4) */ @import "tailwindcss"; @variant dark (&:where(.dark, .dark *));
Using the dark: Prefix
Once configured, apply dark mode styles by prefixing any Tailwind utility class with dark:. The class only applies when the dark class is active on the <html> element.
{/* A card that adapts to dark mode */}
<div className="bg-white dark:bg-gray-800
border border-gray-200 dark:border-gray-700
rounded-2xl p-6 shadow-sm dark:shadow-gray-900/50">
<h2 className="text-xl font-bold text-gray-900 dark:text-white">
Card Title
</h2>
<p className="text-gray-600 dark:text-gray-400 mt-2">
Card description text that adapts to dark mode.
</p>
<button className="mt-4 px-4 py-2 bg-cyan-700 dark:bg-cyan-600
text-white rounded-lg hover:bg-cyan-800 dark:hover:bg-cyan-500">
Action
</button>
</div>Essential Dark Mode Utility Classes
dark:bg-gray-900Dark background (near black)dark:bg-gray-800Slightly lighter dark backgrounddark:text-whiteWhite text in dark modedark:text-gray-300Muted text in dark modedark:border-gray-700Dark border colordark:border-gray-600Slightly lighter dark borderdark:shadow-gray-900/50Dark shadow with opacitydark:placeholder:text-gray-500Placeholder in dark modedark:hover:bg-gray-700Hover state in dark modedark:focus:ring-cyan-400Focus ring color in dark modeDark Mode Toggle in React / Next.js
Here is a simple dark mode toggle that persists user preference in localStorage:
"use client";
import { useEffect, useState } from "react";
export function DarkModeToggle() {
const [dark, setDark] = useState(false);
useEffect(() => {
// Load from localStorage on mount
const stored = localStorage.getItem("theme");
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
const isDark = stored === "dark" || (!stored && prefersDark);
setDark(isDark);
document.documentElement.classList.toggle("dark", isDark);
}, []);
const toggle = () => {
const next = !dark;
setDark(next);
document.documentElement.classList.toggle("dark", next);
localStorage.setItem("theme", next ? "dark" : "light");
};
return (
<button
onClick={toggle}
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
aria-label="Toggle dark mode"
>
{dark ? (
<svg className="w-5 h-5 text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1z..." />
</svg>
) : (
<svg className="w-5 h-5 text-gray-600" fill="currentColor" viewBox="0 0 20 20">
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
</svg>
)}
</button>
);
}Using next-themes for System Preference + Manual Toggle
For production Next.js apps, next-themes is the recommended solution. It handles SSR flash prevention, system preference detection, localStorage persistence, and multi-tab syncing:
# Install
npm install next-themes
// app/layout.tsx
import { ThemeProvider } from "next-themes";
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
</body>
</html>
);
}
// components/DarkModeToggle.tsx
"use client";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
export function DarkModeToggle() {
const { theme, setTheme } = useTheme();
const [mounted, setMounted] = useState(false);
// Avoid hydration mismatch
useEffect(() => setMounted(true), []);
if (!mounted) return null;
return (
<button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
{theme === "dark" ? "☀️ Light" : "🌙 Dark"}
</button>
);
}Dark Mode Component Examples
Converting common components to support dark mode:
Navbar
bg-white border-b border-gray-200dark:bg-gray-900 dark:border-gray-800text-gray-700 hover:text-gray-900dark:text-gray-300 dark:hover:text-whiteCard
bg-white border border-gray-200 shadow-smdark:bg-gray-800 dark:border-gray-700 dark:shadow-gray-900/50text-gray-900dark:text-whiteButton (Primary)
bg-cyan-700 text-white hover:bg-cyan-800dark:bg-cyan-600 dark:hover:bg-cyan-500Input
bg-white border border-gray-300 text-gray-900dark:bg-gray-800 dark:border-gray-600 dark:text-whiteplaceholder:text-gray-400dark:placeholder:text-gray-500Page Background
bg-whitedark:bg-gray-950text-gray-900dark:text-gray-100Frequently Asked Questions
How do I prevent the dark mode flash on page load in Next.js?
Use the next-themes library with suppressHydrationWarning on the <html> element. Alternatively, inject a blocking script in <head> that reads localStorage and applies the dark class before React hydrates.
Can I use dark mode with Tailwind CSS v4?
Yes. In Tailwind v4, configure dark mode with @variant dark (&:where(.dark, .dark *)) in your CSS file. The dark: prefix works identically to v3.
Should I store dark mode preference in localStorage or cookies?
localStorage is fine for most cases. Use cookies if you do server-side rendering and need the preference available on the server (to avoid a flash). next-themes handles both approaches.
How do I apply dark mode to images in Tailwind?
For decorative images, use dark:opacity-80 to slightly dim them. For images that should change completely in dark mode (like logos), use dark:hidden on the light version and hidden dark:block on the dark version.
Why is my Tailwind dark mode not working?
Common causes: (1) darkMode is not set to 'class' in tailwind.config. (2) The 'dark' class is not being applied to <html>. (3) The class is applied but Tailwind hasn't purged the dark: variants. (4) Browser cache — hard refresh with Ctrl+Shift+R.
Browse Free Tailwind CSS Components
All components include Tailwind CSS code you can adapt for dark mode.