Users expect dark mode, and Tailwind makes supporting it a matter of adding dark: classes.
The dark: variant applies a utility when dark mode is active: bg-white dark:bg-gray-900.
By default it follows the operating system setting, compiling to @media (prefers-color-scheme: dark). No JavaScript is needed.
To let users choose, redefine the variant in your CSS so it depends on a class or attribute instead:
@custom-variant dark (&:where(.dark, .dark *));Now dark: applies inside any element with the dark class, usually <html class="dark">. A tiny script in the head sets that class before the page paints (from localStorage or matchMedia) to avoid a flash of the wrong theme. A data attribute works the same way: &:where([data-theme=dark], [data-theme=dark] *).
Practical advice:
- Design the pairs together:
text-gray-900 dark:text-gray-100,border-gray-200 dark:border-white/10. Translucent whites and blacks (bg-white/5) adapt well to layered dark surfaces. - Set
color-scheme(thescheme-light-darkutility or CSS) so form controls and scrollbars match. - For larger apps, define semantic colour variables in
@themeand switch their values in dark mode, then usebg-surfaceandtext-mutedeverywhere instead of writingdark:pairs on every element.
Example
<!-- app.css
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
-->
<html class="dark">
<head>
<script>
// run before paint: saved choice, else system preference
const saved = localStorage.getItem("theme");
const dark = saved ? saved === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
document.documentElement.classList.toggle("dark", dark);
</script>
</head>
<body class="bg-white text-gray-900 dark:bg-gray-950 dark:text-gray-100">
<div class="rounded-xl border border-gray-200 p-6 dark:border-white/10 dark:bg-white/5">
<p class="text-gray-600 dark:text-gray-400">Card text</p>
</div>
</body>
</html>Remember
- dark: follows prefers-color-scheme by default
- @custom-variant dark (...) switches it to a class or attribute
- Set the class in a head script to avoid a flash of the wrong theme
- Semantic colour variables beat dark: pairs in large apps
Watch out for
- Toggling the dark class after the page has rendered, causing a white flash on load.