Dark mode has gone from novelty to expectation: users choose it at the operating-system level and expect sites to respect it. Tailwind CSS makes dark theming almost mechanical with the dark: variant. In this lesson you will learn both dark mode strategies, how to design a dark palette that does not look muddy, and how to build a working theme toggle.
Any utility prefixed with dark: applies only in dark mode:
<div class="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
<h1 class="text-2xl font-bold">Adaptive card</h1>
<p class="text-gray-600 dark:text-gray-400">Same markup, two themes.</p>
</div>You write the light theme normally, then add dark overrides beside each color decision.
By default, dark: responds to the prefers-color-scheme media query. If the visitor has chosen dark at the OS level, dark styles apply automatically. Zero JavaScript, zero configuration — but the user cannot override it per-site.
Most production sites want a toggle button. Configure Tailwind to key dark mode off a class or attribute on the root element instead. In modern Tailwind you register a custom variant in CSS:
@custom-variant dark (&:where(.dark, .dark *));(In Tailwind v3 the equivalent was darkMode: "class" in tailwind.config.js.) Now dark styles apply whenever <html class="dark"> is present, and you control that class with a few lines of JavaScript:
const btn = document.querySelector("#theme-toggle");
btn.addEventListener("click", () => {
document.documentElement.classList.toggle("dark");
localStorage.theme = document.documentElement.classList.contains("dark") ? "dark" : "light";
});Persisting the choice to localStorage and applying it in a tiny inline script in <head> prevents the dreaded flash of the wrong theme on page load.
Straight color inversion looks terrible. A few battle-tested guidelines:
gray-900 or slate-900 backgrounds are easier on the eyes.gray-100 or gray-200 reduces glare.dark:border-gray-700) or lighter surfaces to show elevation.blue-500 button may read better as blue-400 on dark surfaces.A typical component conversion:
<article class="rounded-xl bg-white shadow-md p-6
dark:bg-gray-800 dark:shadow-none dark:border dark:border-gray-700">
<h2 class="text-gray-900 dark:text-white">Title</h2>
<p class="text-gray-600 dark:text-gray-300">Body copy.</p>
<button class="bg-indigo-600 text-white hover:bg-indigo-700
dark:bg-indigo-500 dark:hover:bg-indigo-400">Action</button>
</article>Variants stack in a predictable order: dark:hover:bg-gray-700 styles hover in dark mode, and dark:md:bg-black applies dark styles only at medium screens and up. This composability is what makes a complete dual theme achievable with utilities alone.
Toggle dark mode and walk every page looking for four common bugs: black-on-black text, images with white backgrounds (wrap them or add dark:bg-white rounded), invisible borders, and hard-coded colors in third-party embeds. Fixing these is usually a one-utility patch each.
dark: variant applies utilities only in dark mode.localStorage and apply it early to avoid theme flash.dark: stacks with hover:, breakpoints, and every other variant.Next up: Customizing the Theme and Config, where you will teach Tailwind your brand colors and fonts.