Adding Dark Mode to Your Site with Tailwind

Dark mode isn’t just a trend—it’s a user expectation. Yet many developers overcomplicate its implementation. With Tailwind CSS, adding dark mode to your site requires just a few lines of code and zero framework dependencies.
This tutorial covers both automatic dark mode (respecting system preferences) and manual toggling with user preference persistence. You’ll learn to implement either approach in minutes using Tailwind’s built-in dark variant.
Key Takeaways
- Tailwind’s
dark:
variant enables dark mode with minimal configuration - System-driven dark mode works automatically with zero JavaScript
- Manual toggles give users control over their viewing preference
- localStorage prevents theme flashing on page loads
How Dark Mode Works in Tailwind CSS
The dark: Variant Explained
Tailwind CSS provides a dark:
variant that applies styles conditionally when dark mode is active. Simply prefix any utility class with dark:
to define its dark mode appearance:
<div class="bg-white dark:bg-gray-900">
<h1 class="text-gray-900 dark:text-white">Hello World</h1>
<p class="text-gray-600 dark:text-gray-400">
This text adapts to your theme preference
</p>
</div>
By default, Tailwind uses the prefers-color-scheme
CSS media query to detect the user’s system preference. No configuration needed—it just works.
System-Driven Dark Mode (Automatic)
Using prefers-color-scheme
The simplest dark mode implementation requires zero JavaScript. Tailwind automatically applies dark mode utilities when the user’s operating system is set to dark mode:
<!-- This card will be white in light mode, dark gray in dark mode -->
<article class="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-lg">
<h2 class="text-xl font-bold text-gray-900 dark:text-white">
Automatic Dark Mode
</h2>
<p class="mt-2 text-gray-600 dark:text-gray-300">
This component respects your system theme preference
</p>
</article>
This approach works immediately but has one limitation: users can’t override their system preference on your site specifically.
Discover how at OpenReplay.com.
Manual Dark Mode Toggle
Class-Based Implementation
To give users control, configure Tailwind to use a .dark
class on a parent element. For Tailwind CSS v3.4 and later, add this to your CSS:
@import "tailwindcss";
/* For Tailwind CSS v4+ */
@custom-variant dark (&:where(.dark, .dark *));
For earlier versions of Tailwind (v3.x), configure it in your tailwind.config.js
:
module.exports = {
darkMode: 'class',
// ... rest of your config
}
Now dark mode activates when the dark
class is present on a parent element:
<html class="dark">
<body>
<div class="bg-white dark:bg-black">
<!-- Dark mode is active -->
</div>
</body>
</html>
Toggle dark mode with this JavaScript:
// Toggle dark mode
document.documentElement.classList.toggle('dark');
Data Attribute Method
Some developers prefer data attributes for theme management. Configure Tailwind to use data-theme
:
@import "tailwindcss";
/* For Tailwind CSS v4+ */
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
Then control dark mode via the attribute:
<html data-theme="dark">
<body>
<div class="bg-white dark:bg-black">
<!-- Dark mode active when data-theme="dark" -->
</div>
</body>
</html>
Persisting User Preference
localStorage Implementation
Users expect their theme choice to persist. Here’s a complete solution that remembers preferences while respecting system defaults:
// Check for saved preference or default to system preference
function getThemePreference() {
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
return savedTheme;
}
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
}
// Apply theme on load
function applyTheme(theme) {
if (theme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}
// Initialize theme
applyTheme(getThemePreference());
// Theme toggle function
function toggleTheme() {
const currentTheme = document.documentElement.classList.contains('dark')
? 'dark'
: 'light';
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
applyTheme(newTheme);
localStorage.setItem('theme', newTheme);
}
To prevent a flash of incorrect theme on page load, add this inline script to your <head>
:
<script>
// Prevent FOUC (Flash of Unstyled Content)
if (localStorage.theme === 'dark' ||
(!localStorage.theme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
}
</script>
Best Practices for Dark Mode with Tailwind
Performance: Place theme detection scripts in the <head>
to prevent layout shifts. The inline script above executes before the page renders.
Accessibility: Ensure sufficient color contrast in both themes. Dark mode isn’t just about inverting colors—maintain readability with proper contrast ratios (WCAG AA standard recommends 4.5:1 for normal text).
Testing: Always test both themes during development. Use browser DevTools to quickly toggle prefers-color-scheme
without changing system settings.
Color Consistency: Use Tailwind’s color palette systematically. For example, if you use gray-100
for light backgrounds, consider gray-800
or gray-900
for dark mode equivalents.
Conclusion
Implementing dark mode with Tailwind CSS requires minimal code and no external dependencies. Whether you choose system-driven or manual toggling, the dark variant makes theme-aware styling straightforward. Start with automatic dark mode for simplicity, then add manual controls when users need more control over their viewing experience.
FAQs
Yes, you can implement a three-way toggle that offers light, dark, and system options. Store the user's choice in localStorage and check if they selected system mode to respect OS preferences.
Use CSS filters for simple adjustments or provide alternate image sources. For icons, consider using currentColor in SVGs so they inherit text color, or apply opacity adjustments with Tailwind utilities.
Absolutely. The dark variant works with any Tailwind utility, including custom colors defined in your config. Just prefix any class with dark: to apply it conditionally in dark mode.
Minimal. The dark variant only adds CSS rules, not JavaScript overhead. The main consideration is preventing flash of unstyled content by loading theme detection scripts early in the document head.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.