Skip to main content

Tailwind CSS Dark Mode Guide

Complete reference for implementing dark mode in Tailwind CSS.
Learn configuration, modifiers, toggles, and component patterns.

Setup & Configuration

Enabling Dark Mode

Tailwind CSS supports dark mode out of the box. By default, it uses the prefers-color-scheme CSS media query.

Media Strategy (Default)

// tailwind.config.js
module.exports = {
  darkMode: 'media',
  // ...
}

Pros:

  • Zero JavaScript required
  • Respects system preference automatically
  • Works immediately on page load

Cons:

  • No user toggle without JavaScript
  • Cannot override system preference

Class Strategy

// tailwind.config.js
module.exports = {
  darkMode: 'class',
  // ...
}

Pros:

  • Full control over dark mode
  • Can persist user preference
  • Works with any toggle mechanism

Cons:

  • Requires JavaScript for toggling
  • May flash on initial load if not handled

Tailwind v4 Note: In Tailwind v4, dark mode uses the media strategy by default. To use the class strategy, add @variant dark (&.dark) to your CSS.

Using the dark: Modifier

Basic Usage

Prefix any utility class with dark: to apply it only in dark mode.

<div class="bg-white dark:bg-gray-900">
  <h1 class="text-gray-900 dark:text-white">Hello World</h1>
  <p class="text-gray-600 dark:text-gray-400">Welcome to dark mode!</p>
</div>

Combining with Other Modifiers

Stack dark: with hover, focus, and responsive modifiers.

<!-- Hover states -->
<button class="bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700">
  Click me
</button>

<!-- Focus states -->
<input class="border-gray-300 focus:border-blue-500
              dark:border-gray-600 dark:focus:border-blue-400" />

<!-- Responsive + dark -->
<div class="bg-white md:bg-gray-50 dark:bg-gray-900 dark:md:bg-gray-800">
  Responsive dark mode
</div>

Common Dark Mode Patterns

Background

bg-white dark:bg-gray-900

Text Color

text-gray-900 dark:text-gray-100

Border

border-gray-200 dark:border-gray-700

Hover State

hover:bg-gray-100 dark:hover:bg-gray-800

Common Utility Pairs

Reference table of commonly paired light and dark mode utilities. Click any row to copy both classes.

PurposeLight ModeDark Mode
Backgroundbg-whitedark:bg-gray-900
Surfacebg-gray-50dark:bg-gray-800
Elevatedbg-gray-100dark:bg-gray-700
Primary texttext-gray-900dark:text-gray-100
Secondary texttext-gray-700dark:text-gray-300
Muted texttext-gray-500dark:text-gray-400
Borderborder-gray-200dark:border-gray-700
Border strongborder-gray-300dark:border-gray-600
Dividerdivide-gray-200dark:divide-gray-700
Ringring-gray-300dark:ring-gray-600
Placeholderplaceholder-gray-400dark:placeholder-gray-500
Shadowshadow-smdark:shadow-gray-900/20

JavaScript Toggle

Basic Toggle Function

When using darkMode: 'class', toggle the dark class on the <html> element.

// Toggle dark mode
function toggleDarkMode() {
  document.documentElement.classList.toggle('dark');
}

// Set dark mode explicitly
function setDarkMode(isDark) {
  if (isDark) {
    document.documentElement.classList.add('dark');
  } else {
    document.documentElement.classList.remove('dark');
  }
}

// Check current state
function isDarkMode() {
  return document.documentElement.classList.contains('dark');
}

React Toggle Hook

import { useState, useEffect } from 'react';

function useDarkMode() {
  const [isDark, setIsDark] = useState(false);

  useEffect(() => {
    // Check initial preference
    const isDarkMode = document.documentElement.classList.contains('dark');
    setIsDark(isDarkMode);
  }, []);

  const toggle = () => {
    setIsDark(!isDark);
    document.documentElement.classList.toggle('dark');
  };

  return { isDark, toggle };
}

// Usage
function ThemeToggle() {
  const { isDark, toggle } = useDarkMode();

  return (
    <button onClick={toggle}>
      {isDark ? '🌙' : '☀️'}
    </button>
  );
}

Persisting User Preference

localStorage Implementation

Save the user's preference to localStorage and restore it on page load.

// Save preference
function setTheme(theme) {
  localStorage.setItem('theme', theme);
  if (theme === 'dark') {
    document.documentElement.classList.add('dark');
  } else {
    document.documentElement.classList.remove('dark');
  }
}

// Load preference on page load
function initTheme() {
  const savedTheme = localStorage.getItem('theme');

  if (savedTheme) {
    setTheme(savedTheme);
  } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
    // Fallback to system preference
    setTheme('dark');
  }
}

// Call on page load
initTheme();

Prevent Flash of Wrong Theme

Add this script in the <head> to prevent the flash of light mode before JavaScript loads.

<head>
  <!-- Add this script BEFORE any stylesheets -->
  <script>
    if (localStorage.theme === 'dark' ||
        (!('theme' in localStorage) &&
         window.matchMedia('(prefers-color-scheme: dark)').matches)) {
      document.documentElement.classList.add('dark');
    } else {
      document.documentElement.classList.remove('dark');
    }
  </script>

  <!-- Your stylesheets -->
  <link rel="stylesheet" href="/styles.css" />
</head>

Three-way Toggle (Light / Dark / System)

function setTheme(theme) {
  if (theme === 'system') {
    localStorage.removeItem('theme');
    const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
    document.documentElement.classList.toggle('dark', prefersDark);
  } else {
    localStorage.setItem('theme', theme);
    document.documentElement.classList.toggle('dark', theme === 'dark');
  }
}

// Listen for system preference changes
window.matchMedia('(prefers-color-scheme: dark)')
  .addEventListener('change', (e) => {
    if (!localStorage.getItem('theme')) {
      document.documentElement.classList.toggle('dark', e.matches);
    }
  });

Component Examples

Card Component

Card Title

This is a card component that adapts to dark mode using Tailwind utilities.

<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4">
  <h4 class="text-gray-900 dark:text-white font-semibold mb-2">Card Title</h4>
  <p class="text-gray-600 dark:text-gray-400 text-sm">Card description...</p>
  <button class="mt-4 px-4 py-2 bg-blue-500 dark:bg-blue-600
                 hover:bg-blue-600 dark:hover:bg-blue-700
                 text-white rounded-lg">
    Action
  </button>
</div>

Button Component

<!-- Primary Button -->
<button class="bg-blue-500 dark:bg-blue-600
               hover:bg-blue-600 dark:hover:bg-blue-700
               text-white px-4 py-2 rounded-lg">
  Primary
</button>

<!-- Secondary Button -->
<button class="border border-gray-300 dark:border-gray-600
               text-gray-700 dark:text-gray-300
               hover:bg-gray-100 dark:hover:bg-gray-800
               px-4 py-2 rounded-lg">
  Secondary
</button>

<!-- Ghost Button -->
<button class="text-gray-600 dark:text-gray-400
               hover:text-gray-900 dark:hover:text-gray-200
               hover:bg-gray-100 dark:hover:bg-gray-800
               px-4 py-2 rounded-lg">
  Ghost
</button>

Input Component

<label class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-2">
  Email Address
</label>
<input
  type="email"
  placeholder="you@example.com"
  class="w-full px-4 py-2 rounded-lg border
         bg-white dark:bg-gray-800
         border-gray-300 dark:border-gray-600
         text-gray-900 dark:text-white
         placeholder-gray-400 dark:placeholder-gray-500
         focus:border-blue-500 dark:focus:border-blue-400
         outline-none"
/>

Navigation Component

<nav class="bg-white dark:bg-gray-900 border-b border-gray-100 dark:border-gray-800 px-6 py-4">
  <div class="flex items-center justify-between">
    <span class="font-bold text-lg text-gray-900 dark:text-white">Logo</span>
    <div class="flex gap-6">
      <a href="#" class="text-sm font-medium
                         text-gray-600 dark:text-gray-300
                         hover:text-gray-900 dark:hover:text-white">
        Home
      </a>
      <a href="#" class="text-sm font-medium
                         text-gray-600 dark:text-gray-300
                         hover:text-gray-900 dark:hover:text-white">
        About
      </a>
    </div>
  </div>
</nav>

Dark Mode Tips

Start with semantic colors: Use bg-white dark:bg-gray-900 for backgrounds and layer up from there.

Test both modes: Always test your UI in both light and dark mode during development.

Consider contrast: Ensure text has sufficient contrast against backgrounds in both modes.

Use opacity for subtle variations: text-white/80 works great for secondary text in dark mode.