Tailwind CSS Dark Mode

Learn how to implement dark mode in your web applications using Tailwind CSS.

Enabling Dark Mode in Tailwind

Tailwind CSS provides built-in support for dark mode styling. You can choose between class-based or media-query-based dark mode.

Configuration in tailwind.config.js

// tailwind.config.js
module.exports = {
  darkMode: 'class', // or 'media' for media query based dark mode
  // rest of your configuration
}

Two options for dark mode:

  • 'media': Uses the prefers-color-scheme media query to detect the user's system preference
  • 'class': Requires manually adding a dark class to the HTML or body element (recommended for toggle functionality)

Using Dark Mode Variants

Once dark mode is enabled, you can use the dark: variant to apply styles specifically for dark mode.

Basic Dark Mode Example

<div class="bg-white dark:bg-gray-800 text-gray-900 dark:text-white p-6 rounded-lg shadow-md">
  <h2 class="text-2xl font-bold mb-4">This is a card</h2>
  <p>This card will have a white background with dark text in light mode, and a dark background with light text in dark mode.</p>
</div>

Light Mode

This is a card

White background with dark text in light mode.

Secondary container with lighter background

Dark Mode

This is a card

Dark background with light text in dark mode.

Secondary container with lighter background

Combining Dark Mode with Other Variants

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

<div class="text-sm md:text-base dark:text-gray-300 md:dark:text-gray-200">
  This text combines responsive and dark mode variants.
</div>

Implementing a Dark Mode Toggle

Add a toggle button to let users switch between light and dark mode.

Dark Mode Toggle Button

<!-- Dark mode toggle button -->
<button id="theme-toggle" class="p-2 rounded-full">
  <!-- Sun icon (shown in dark mode) -->
  <svg id="sun-icon" class="hidden dark:block w-6 h-6 text-yellow-300" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
    <path fill-rule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clip-rule="evenodd"></path>
  </svg>
  <!-- Moon icon (shown in light mode) -->
  <svg id="moon-icon" class="block dark:hidden w-6 h-6 text-gray-800" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
    <path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path>
  </svg>
</button>

Try the dark mode toggle:

Interactive Demo

This demo lets you toggle between light and dark mode. The toggle only affects this container.

Card Example

This card changes appearance based on the current theme.

Button Examples

JavaScript for Dark Mode Toggle

// JavaScript to toggle dark mode
document.getElementById('theme-toggle').addEventListener('click', function() {
  // Toggle dark class on the html element
  document.documentElement.classList.toggle('dark');
  
  // Store user preference in localStorage
  if (document.documentElement.classList.contains('dark')) {
    localStorage.setItem('color-theme', 'dark');
  } else {
    localStorage.setItem('color-theme', 'light');
  }
});

// Check for saved theme preference or system preference
const themePreference = () => {
  // Check localStorage
  if (localStorage.getItem('color-theme') === 'dark' || 
      (!localStorage.getItem('color-theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
    document.documentElement.classList.add('dark');
  } else {
    document.documentElement.classList.remove('dark');
  }
};

// Set theme on page load
themePreference();

Dark Mode Color Strategies

Effective color choices for dark mode that maintain accessibility and visual hierarchy.

Common Dark Mode Color Patterns

/* Light mode colors */
.bg-primary { @apply bg-white; }
.bg-secondary { @apply bg-gray-100; }
.bg-accent { @apply bg-blue-500; }
.text-primary { @apply text-gray-900; }
.text-secondary { @apply text-gray-600; }
.border-primary { @apply border-gray-200; }

/* Dark mode colors */
.dark .bg-primary { @apply bg-gray-900; }
.dark .bg-secondary { @apply bg-gray-800; }
.dark .bg-accent { @apply bg-blue-600; }
.dark .text-primary { @apply text-gray-100; }
.dark .text-secondary { @apply text-gray-400; }
.dark .border-primary { @apply border-gray-700; }

Complete Dark Mode Example

<div class="bg-white dark:bg-gray-900 min-h-screen">
  <header class="bg-gray-100 dark:bg-gray-800 shadow-sm">
    <div class="container mx-auto px-4 py-6">
      <h1 class="text-2xl font-bold text-gray-900 dark:text-white">My Application</h1>
    </div>
  </header>
  
  <main class="container mx-auto px-4 py-8">
    <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-6">
      <h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">Welcome</h2>
      <p class="text-gray-700 dark:text-gray-300">This is an example of a page with dark mode support.</p>
      <button class="mt-4 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
        Get Started
      </button>
    </div>
    
    <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
      <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 border border-gray-200 dark:border-gray-700">
        <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-3">Features</h3>
        <ul class="text-gray-700 dark:text-gray-300 space-y-2">
          <li>Responsive design</li>
          <li>Dark mode support</li>
          <li>Accessible components</li>
        </ul>
      </div>
      
      <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 border border-gray-200 dark:border-gray-700">
        <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-3">Statistics</h3>
        <div class="text-gray-700 dark:text-gray-300">
          <p>Users: 1,234</p>
          <p>Active: 789</p>
          <p>Conversion: 12.3%</p>
        </div>
      </div>
    </div>
  </main>
  
  <footer class="bg-gray-100 dark:bg-gray-800 mt-8 py-6">
    <div class="container mx-auto px-4 text-gray-600 dark:text-gray-400 text-center">
      <p>© 2025 My Application. All rights reserved.</p>
    </div>
  </footer>
</div>

Accessibility Considerations

Ensuring your dark mode implementation is accessible to all users.

Color Contrast

Maintain sufficient contrast ratios in both light and dark modes:

  • Normal text: minimum contrast ratio of 4.5:1
  • Large text: minimum contrast ratio of 3:1

Focus Indicators

<!-- Focus indicators that work in both modes -->
<button class="focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:ring-offset-2 focus:ring-offset-white dark:focus:ring-offset-gray-800">
  Accessible Button
</button>

Respecting User Preferences

Always respect the user's system preference when initially loading the page, then allow them to override it if desired.

// Check system preference first, then localStorage
const prefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
const storedTheme = localStorage.getItem('color-theme');

if (storedTheme === 'dark' || (!storedTheme && prefersDarkMode)) {
  document.documentElement.classList.add('dark');
} else {
  document.documentElement.classList.remove('dark');
}

Advanced Dark Mode Techniques

Beyond the basics: advanced techniques for dark mode implementation.

Custom Properties (CSS Variables)

/* Define variables in :root (light mode) */
:root {
  --color-bg-primary: 255, 255, 255;
  --color-bg-secondary: 243, 244, 246;
  --color-text-primary: 31, 41, 55;
  --color-text-secondary: 107, 114, 128;
}

/* Override variables in dark mode */
.dark {
  --color-bg-primary: 31, 41, 55;
  --color-bg-secondary: 17, 24, 39;
  --color-text-primary: 243, 244, 246;
  --color-text-secondary: 156, 163, 175;
}

/* Use variables with rgb() */
.bg-primary { background-color: rgb(var(--color-bg-primary)); }
.text-primary { color: rgb(var(--color-text-primary)); }

/* Use with opacity */
.bg-primary-90 { background-color: rgba(var(--color-bg-primary), 0.9); }

Dark Mode for Images

<!-- Option 1: Different images for different modes -->
<img src="/light-logo.png" class="block dark:hidden" alt="Logo">
<img src="/dark-logo.png" class="hidden dark:block" alt="Logo">

<!-- Option 2: CSS filters -->
<img src="/logo.png" class="dark:invert" alt="Logo">

Transition Between Modes

<!-- Add transition to specific properties -->
<div class="bg-white text-gray-900 dark:bg-gray-800 dark:text-white transition-colors duration-300">
  Content that smoothly transitions between light and dark mode
</div>

<!-- Or add to the html element for global transitions -->
<style>
  html.transitioning * {
    transition-property: background-color, border-color, color, fill, stroke;
    transition-duration: 300ms;
  }
</style>

<script>
  // Add this before toggling the 'dark' class
  document.documentElement.classList.add('transitioning');
  
  // Remove after transition completes
  setTimeout(() => {
    document.documentElement.classList.remove('transitioning');
  }, 300);
</script>

For more Tailwind CSS topics, check out our Fundamentals, Customization, UI Components, and Responsive Design pages.