← Back to Tailwind CSS Mastery
Intermediate12 min read

Dark Mode

Implement dark mode with Tailwind dark variant — system preference detection, manual toggles, and cohesive dark color schemes.

Dark Variant Basics

Prefix any utility with dark: to apply it in dark mode. dark:bg-gray-900 dark:text-white flips background and text colors. Dark mode styles cascade like any variant — combine with hover:, focus:, and responsive prefixes.

Configure dark mode strategy in tailwind.config.js: "media" (system preference) or "class" (manual toggle via a class on html or body).

  • class strategy enables user toggle independent of OS setting
  • media strategy respects prefers-color-scheme automatically
  • Always pair background and text dark: variants together
// tailwind.config.js
module.exports = {
  darkMode: 'class', // or 'media'
};

<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
  Adapts to dark mode
</div>

System Preference Detection

With darkMode: "media", Tailwind generates @media (prefers-color-scheme: dark) rules. No JavaScript needed — the OS setting drives the theme.

This is the simplest approach but does not allow user override. Most production apps use class strategy with a toggle that respects system preference as the default.

/* Generated with darkMode: 'media' */
@media (prefers-color-scheme: dark) {
  .dark\:bg-gray-900 { background-color: #111827; }
}

Manual Toggle Implementation

Add or remove the "dark" class on the html element to toggle themes. Read localStorage for persistence and matchMedia for system default. Toggle before page paint to prevent flash of wrong theme.

Use a small inline script in head to set the class before render. Frameworks like next-themes handle this automatically for React and Next.js.

// Toggle dark mode
function toggleDark() {
  document.documentElement.classList.toggle('dark');
  localStorage.theme =
    document.documentElement.classList.contains('dark') ? 'dark' : 'light';
}

// Inline in <head> to prevent flash
// if (localStorage.theme === 'dark') document.documentElement.classList.add('dark');

Dark Color Schemes

Design dark palettes deliberately — do not invert light colors. Use dark grays (gray-900, gray-800) for surfaces, not pure black. Reduce contrast slightly: text-gray-300 instead of text-white for body copy.

Borders need adjustment too: border-gray-200 becomes dark:border-gray-700. Shadows become less visible — use border separation instead of shadow in dark mode.

<div class="bg-white dark:bg-gray-900
  border border-gray-200 dark:border-gray-700
  shadow-sm dark:shadow-none
  rounded-xl p-6">
  <h2 class="text-gray-900 dark:text-white">Card Title</h2>
  <p class="text-gray-600 dark:text-gray-300">Body text</p>
</div>

Dark Mode Best Practices

Test both modes during development, not as an afterthought. Use semantic color tokens (bg-surface, text-primary) in your config so dark variants are defined once in the theme, not repeated on every element.

Images and illustrations may need dark variants. Use dark:opacity-80 or separate dark-mode assets for logos and icons that assume a light background.

  • Avoid pure #000 backgrounds — use gray-900 or gray-950
  • Reduce image brightness in dark mode: dark:brightness-90
  • Test contrast ratios in both modes for WCAG compliance
// Define semantic tokens in config
colors: {
  surface: { DEFAULT: '#ffffff', dark: '#111827' },
  'text-primary': { DEFAULT: '#111827', dark: '#f9fafb' },
}

Get In Touch


Ready to discuss your next project? Drop me a message.