Tailwind CSS Cheatsheet

Dark Mode

Use this Tailwind CSS reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

How Dark Mode Works

Tailwind's dark: variant activates dark-mode styles. The trigger strategy determines when it activates.

Strategy: Media Query (default)

Activates when the OS/browser is set to dark mode. No configuration needed in v4; in v3 this is the default.

<div class="bg-white text-slate-900 dark:bg-slate-900 dark:text-white">
  Adapts to OS preference
</div>

Strategy: Class Toggle (v3)

Activate dark mode by adding a class to <html> — gives you manual control.

// tailwind.config.js (v3)
module.exports = {
  darkMode: 'class',
  // ...
}
<!-- Add 'dark' class to html element to activate -->
<html class="dark">
  <body>
    <div class="dark:bg-slate-900">Dark background</div>
  </body>
</html>

Toggle via JavaScript:

<script>
  // Toggle dark mode
  document.documentElement.classList.toggle('dark')

  // Set from localStorage
  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>

Strategy: Data Attribute (v3.4+ / v4)

Use a data-* attribute on the root element instead of a class — avoids class conflicts.

// tailwind.config.js (v3.4+)
module.exports = {
  darkMode: ['selector', '[data-theme="dark"]'],
}
/* v4 — in your CSS */
@import "tailwindcss";
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
<html data-theme="dark">
  <div class="dark:bg-slate-900">Activated by data attribute</div>
</html>

Strategy: Class Toggle (v4)

Redefine the dark variant to key off a class instead of the media query:

@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
<div class="dark">
  <p class="text-slate-900 dark:text-white">Only dark inside .dark container</p>
</div>

Using the dark: Variant

Any utility can be prefixed with dark::

<!-- Colors -->
<body class="bg-white text-slate-900 dark:bg-slate-950 dark:text-slate-50">

<!-- Borders -->
<div class="border border-slate-200 dark:border-slate-700">

<!-- Shadows -->
<div class="shadow-md dark:shadow-slate-900/50">

<!-- Typography -->
<p class="text-slate-600 dark:text-slate-400">Muted text</p>
<h2 class="text-slate-900 dark:text-white">Heading</h2>

<!-- Backgrounds with opacity -->
<div class="bg-slate-100 dark:bg-slate-800">Surface</div>
<div class="bg-white/80 dark:bg-slate-900/80">Translucent</div>

<!-- Interactive states in dark mode -->
<button class="bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-400 text-white">
  Button
</button>

Common Dark Mode Patterns

Page background and text

<html class="bg-white text-slate-900 dark:bg-slate-950 dark:text-slate-50">

Card component

<div class="bg-white border border-slate-200 rounded-xl shadow-sm p-6
            dark:bg-slate-800 dark:border-slate-700 dark:shadow-slate-900/50">
  <h3 class="font-semibold text-slate-900 dark:text-white">Card Title</h3>
  <p class="text-slate-600 dark:text-slate-400 mt-1">Card description</p>
</div>

Input field

<input
  class="w-full border border-slate-300 rounded-lg px-3 py-2
         bg-white text-slate-900 placeholder:text-slate-400
         focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent
         dark:bg-slate-800 dark:border-slate-600 dark:text-white dark:placeholder:text-slate-500"
  type="text"
  placeholder="Search..."
>

Navigation

<nav class="bg-white border-b border-slate-200 dark:bg-slate-900 dark:border-slate-700">
  <a href="/" class="text-slate-700 hover:text-slate-900 dark:text-slate-300 dark:hover:text-white">
    Home
  </a>
</nav>

Code block

<pre class="bg-slate-100 dark:bg-slate-900 rounded-lg p-4 text-sm overflow-x-auto">
  <code class="text-slate-800 dark:text-slate-200">code here</code>
</pre>

Badge / pill

<span class="bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400
             text-xs font-medium px-2.5 py-0.5 rounded-full">
  Badge
</span>

Frosted glass in dark mode

<div class="bg-white/80 backdrop-blur-md border border-slate-200/60 rounded-2xl
            dark:bg-slate-900/80 dark:border-slate-700/60">
  Frosted glass
</div>

Prevent Flash of Wrong Theme

Add an inline script before any CSS loads to read the stored preference:

<head>
  <script>
    // Prevent FOUC
    const theme = localStorage.getItem('theme')
    if (theme === 'dark' || (!theme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
      document.documentElement.classList.add('dark')
    }
  </script>
  <!-- CSS loads after — no flash -->
</head>

Combining dark: with Other Variants

Dark mode stacks with any other variant:

<!-- dark + hover -->
<button class="dark:hover:bg-slate-700">...</button>

<!-- dark + responsive -->
<div class="dark:md:text-lg">...</div>

<!-- dark + focus -->
<input class="dark:focus:ring-blue-400">

<!-- dark + group-hover -->
<div class="group">
  <p class="group-hover:text-blue-600 dark:group-hover:text-blue-400">...</p>
</div>

<!-- dark + disabled -->
<button class="dark:disabled:opacity-40" disabled>...</button>

<!-- dark + first-child -->
<li class="dark:first:border-t-0">...</li>

System-Level vs. Manual Toggle

Using both (respect system unless user has picked)

<script>
  // On page load
  function applyTheme(theme) {
    if (theme === 'dark') {
      document.documentElement.classList.add('dark')
    } else {
      document.documentElement.classList.remove('dark')
    }
  }

  const stored = localStorage.getItem('theme')
  if (stored) {
    applyTheme(stored)
  } else {
    // System preference
    applyTheme(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
  }
</script>

Theme toggle button

<button
  onclick="
    const isDark = document.documentElement.classList.toggle('dark');
    localStorage.setItem('theme', isDark ? 'dark' : 'light');
  "
  class="p-2 rounded-lg text-slate-500 hover:text-slate-900 dark:text-slate-400 dark:hover:text-white transition-colors"
  aria-label="Toggle dark mode"
>
  <!-- Sun icon (shown in dark mode) -->
  <svg class="hidden dark:block size-5" ...></svg>
  <!-- Moon icon (shown in light mode) -->
  <svg class="block dark:hidden size-5" ...></svg>
</button>

Swapping Images for Dark Mode

<!-- CSS display swap approach -->
<img class="block dark:hidden" src="logo-light.png" alt="Logo">
<img class="hidden dark:block" src="logo-dark.png" alt="Logo">

<!-- CSS filter approach (quick invert) -->
<img class="dark:invert" src="logo-black.svg" alt="Logo">

<!-- picture element (no JS, respects media query) -->
<picture>
  <source srcset="logo-dark.png" media="(prefers-color-scheme: dark)">
  <img src="logo-light.png" alt="Logo">
</picture>

Dark Mode Color Palette Tips

LightDark equivalent
bg-whitedark:bg-slate-950
bg-slate-50 (surface)dark:bg-slate-900
bg-slate-100 (raised)dark:bg-slate-800
text-slate-900 (primary)dark:text-white
text-slate-700 (body)dark:text-slate-300
text-slate-500 (muted)dark:text-slate-400
border-slate-200dark:border-slate-700
border-slate-100dark:border-slate-800

Using CSS variables for dark mode tokens (v4 recommended approach) — register the tokens once in @theme, then override the plain CSS variables under your dark selector:

@theme {
  --color-surface: #ffffff;
  --color-on-surface: #0f172a;
}

@layer base {
  .dark {
    --color-surface: #0f172a;
    --color-on-surface: #f8fafc;
  }
}
<div class="bg-surface text-on-surface">Themed automatically</div>