Dark Mode
In this chapter, you learn how to implement dark mode with Tailwind CSS.
Dark mode basics
Use the dark: variant to specify styles for dark mode.
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
White background in light mode, dark background in dark mode
</div>
Switching approaches
Approach 1: Media query (default)
Switches automatically based on the OS setting.
/* Default behavior in v4 */
@import "tailwindcss";
Approach 2: Class-based
Watch out for FOUC (Flash of Unstyled Content)
With the class-based approach, on page load you may see a brief flash of light mode until the JavaScript runs, known as "FOUC (Flash of Unstyled Content)." To prevent this, read the localStorage value in a <script> tag inside <head> and run the code that adds the class to <html> synchronously.
Control manually with the class attribute on the HTML.
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
<!-- Add the dark class to the root element -->
<html class="dark">
<body class="bg-white dark:bg-gray-900">
...
</body>
</html>
Approach 3: Selector-based
Control dark mode with an arbitrary selector.
@import "tailwindcss";
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
<html data-theme="dark">
...
</html>
Implementation patterns
Switching with JavaScript
<button id="theme-toggle" class="p-2 rounded-lg bg-gray-200 dark:bg-gray-700">
<span class="hidden dark:block">🌙</span>
<span class="block dark:hidden">☀️</span>
</button>
<script>
const toggle = document.getElementById('theme-toggle');
toggle.addEventListener('click', () => {
document.documentElement.classList.toggle('dark');
});
</script>
Follow the system setting + manual switching
// Initialization
if (localStorage.theme === 'dark' ||
(!('theme' in localStorage) &&
window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
// Switch to light mode manually
localStorage.theme = 'light';
document.documentElement.classList.remove('dark');
// Switch to dark mode manually
localStorage.theme = 'dark';
document.documentElement.classList.add('dark');
// Follow the system setting
localStorage.removeItem('theme');
Color design
A color palette for dark mode
<!-- Background colors -->
<div class="bg-white dark:bg-gray-900">Level 1</div>
<div class="bg-gray-50 dark:bg-gray-800">Level 2</div>
<div class="bg-gray-100 dark:bg-gray-700">Level 3</div>
<!-- Text colors -->
<p class="text-gray-900 dark:text-white">Main text</p>
<p class="text-gray-600 dark:text-gray-300">Secondary text</p>
<p class="text-gray-400 dark:text-gray-500">Supplementary text</p>
<!-- Border -->
<div class="border-gray-200 dark:border-gray-700">Border</div>
Recommended color mapping
| Use case | Light | Dark |
|---|---|---|
| Background (base) | white | gray-900 |
| Background (secondary) | gray-50 | gray-800 |
| Background (tertiary) | gray-100 | gray-700 |
| Text (main) | gray-900 | white |
| Text (secondary) | gray-600 | gray-300 |
| Text (supplementary) | gray-400 | gray-500 |
| Border | gray-200 | gray-700 |
| Hover background | gray-100 | gray-800 |
A color system using CSS variables
In v4, you can manage theme colors with CSS variables.
@import "tailwindcss";
@theme {
/* Default values for light mode */
--color-bg-primary: #ffffff;
--color-bg-secondary: #f9fafb;
--color-text-primary: #111827;
--color-text-secondary: #4b5563;
}
/* Values for dark mode */
.dark {
--color-bg-primary: #111827;
--color-bg-secondary: #1f2937;
--color-text-primary: #ffffff;
--color-text-secondary: #d1d5db;
}
<div class="bg-(--color-bg-primary) text-(--color-text-primary)">
Using CSS variables
</div>
Practical examples
Header
<header class="bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700">
<nav class="max-w-7xl mx-auto px-4 h-16 flex items-center justify-between">
<a href="/" class="font-bold text-xl text-gray-900 dark:text-white">
Logo
</a>
<div class="flex items-center gap-4">
<a href="#" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white">
About
</a>
<!-- Theme toggle button -->
<button class="p-2 rounded-lg bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700">
<span class="dark:hidden">🌙</span>
<span class="hidden dark:inline">☀️</span>
</button>
</div>
</nav>
</header>
Card
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg dark:shadow-gray-900/50 overflow-hidden">
<img src="..." class="w-full h-48 object-cover" />
<div class="p-6">
<span class="text-sm text-blue-600 dark:text-blue-400 font-semibold">
Category
</span>
<h3 class="mt-2 text-xl font-bold text-gray-900 dark:text-white">
Card title
</h3>
<p class="mt-2 text-gray-600 dark:text-gray-300">
Description goes here...
</p>
<button class="mt-4 px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg">
View details
</button>
</div>
</div>
Form
<form class="bg-white dark:bg-gray-800 p-6 rounded-xl shadow-lg">
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-6">
Contact us
</h2>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Name
</label>
<input
type="text"
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600
bg-white dark:bg-gray-700 text-gray-900 dark:text-white
focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Message
</label>
<textarea
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600
bg-white dark:bg-gray-700 text-gray-900 dark:text-white
focus:ring-2 focus:ring-blue-500 focus:border-transparent"
rows="4"
></textarea>
</div>
<button class="w-full py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg">
Submit
</button>
</div>
</form>
Table
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg overflow-hidden">
<table class="w-full">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Name
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Status
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
<td class="px-6 py-4 text-gray-900 dark:text-white">Taro Tanaka</td>
<td class="px-6 py-4">
<span class="px-2 py-1 text-xs rounded-full bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200">
Active
</span>
</td>
</tr>
</tbody>
</table>
</div>
Modal
<!-- Overlay -->
<div class="fixed inset-0 bg-black/50 dark:bg-black/70 flex items-center justify-center">
<!-- Modal -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-xl max-w-lg w-full mx-4 p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-gray-900 dark:text-white">
Modal title
</h2>
<button class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200">
✕
</button>
</div>
<p class="text-gray-600 dark:text-gray-300">
Modal content...
</p>
<div class="mt-6 flex justify-end gap-3">
<button class="px-4 py-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg">
Cancel
</button>
<button class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg">
Confirm
</button>
</div>
</div>
</div>
Dark mode best practices
1. Be mindful of contrast
<!-- Good: sufficient contrast -->
<p class="text-gray-900 dark:text-white">Main text</p>
<!-- Avoid: low contrast -->
<p class="text-gray-500 dark:text-gray-600">Hard-to-read text</p>
2. Avoid pure black and white
<!-- Good: soft colors -->
<div class="bg-gray-50 dark:bg-gray-900">...</div>
<!-- Avoid: pure black and white -->
<div class="bg-white dark:bg-black">...</div>
3. Adjust shadows
<!-- Make shadows darker in dark mode -->
<div class="shadow-lg dark:shadow-gray-900/50">...</div>
4. Adjust image brightness
<img src="..." class="dark:brightness-90" />
Summary
- Specify dark mode styles with the
dark:variant - You can choose between the media query approach and the class approach
- Map your color palette appropriately
- Be mindful of contrast and readability
- You can centrally manage theme colors with CSS variables
What to read next
- Container queries — self-contained responsiveness at the component level
- Customization — define your own colors with
@theme - Tailwind CSS Guide table of contents — back to the structure of all 18 chapters