Capítulo 3 de 29

Chapter 3: Dark Mode

Core Idea

The dark: variant styles an element differently when dark mode is active; by default it tracks the OS-level prefers-color-scheme media feature, but it can be overridden to be driven by a class or data attribute instead for manual/three-way toggles.

Key Concepts

  • Default behavior: dark:bg-gray-800 etc. apply automatically based on prefers-color-scheme: dark — no configuration needed to get OS-driven dark mode.
  • Manual toggling via class: override the variant with @custom-variant dark (&:where(.dark, .dark *)); in your CSS entry file — after this, dark:* utilities apply whenever an ancestor (or the element itself) has the dark class, regardless of OS preference.
  • Manual toggling via data attribute: same idea with @custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *)); — applies when data-theme="dark" is set up the tree.
  • Three-way (light/dark/system) toggle: combine the class-based override with window.matchMedia('(prefers-color-scheme: dark)') in JS — read/write the preference (commonly to localStorage), and toggle the dark class on <html> on load (inline in <head> to avoid a flash of unstyled content) and whenever the user changes it. Absence of a stored preference should fall back to the OS media query result.

Code Examples

/* app.css — manual class-based dark mode */
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
// inline in <head>, before paint, to avoid FOUC
document.documentElement.classList.toggle(
  "dark",
  localStorage.theme === "dark" ||
    (!("theme" in localStorage) && window.matchMedia("(prefers-color-scheme: dark)").matches),
);
  • What it demonstrates: switching dark: from media-query-driven to class-driven, and the standard three-way toggle pattern including flash-of-unstyled-content avoidance.

Key Takeaways

  1. Zero-config dark mode just works via prefers-color-scheme — only override the dark variant when you need a manual toggle.
  2. The override is a one-line @custom-variant dark (...) — no separate dark-mode plugin or config file.
  3. Persist the toggle choice (e.g. localStorage) and read it inline before first paint to avoid a flash of the wrong theme.
  4. The storage/rendering strategy is entirely up to the developer — it can just as well be a server-rendered class based on a database preference.

Connects To

  • Adding Custom Styles: @custom-variant is the general mechanism for overriding/adding variants; dark mode is its most common built-in use case.
  • Hover, Focus & Other States: dark: composes with every other variant the same way hover:/lg: do (e.g. dark:hover:bg-gray-700).