Fixing Tailwind Dark Mode with Custom Variants
TIL: Fixing Tailwind Dark Mode with Custom Variants
Today I learned how to fix a frustrating issue where Tailwind's dark mode colors weren't working correctly when my manual theme toggle conflicted with the system's prefers-color-scheme setting.
The Problem
I had implemented a manual dark mode toggle using a .dark class on the <html> element. However, even when I selected "Light Mode," the text was showing white/light colors because my system was set to dark mode. The CSS media query @media (prefers-color-scheme: dark) was overriding my manual theme selection!
In other words: If your system preference was dark, the site would apply dark mode styles even when you explicitly chose light mode.
The Root Cause
By default, Tailwind v4 applies dark mode utilities based on prefers-color-scheme: dark, which checks the user's operating system settings. This means even with darkMode: 'class' in your config, the system preference could still interfere.
The Solution
Override the dark mode variant in your CSS to use only the .dark class selector, ignoring prefers-color-scheme entirely:
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
This single line tells Tailwind:
- Only apply
dark:*utilities when the.darkclass is present - Ignore the system's
prefers-color-schemesetting - Give you full control over theme switching via JavaScript
How It Works
The @custom-variant directive creates a custom variant that:
- Checks for
.darkclass on the element itself (&:where(.dark)) - Or checks for
.darkclass on any ancestor (&:where(.dark *)) - Applies dark mode styles only when this condition is met
Three-State Theme System
With this fix, you can now implement a proper three-state theme system:
// light: Force light mode (ignore system preference)
// dark: Force dark mode (ignore system preference)
// system: Respect prefers-color-scheme
if (theme === "system") {
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
.matches ? "dark" : "light";
document.documentElement.classList.add(systemTheme);
} else {
document.documentElement.classList.add(theme);
}
References
Key Takeaway
When implementing manual dark mode with Tailwind, always override the dark variant to prevent prefers-color-scheme from interfering with your theme toggle. This gives you complete control over your theme system! 🎨