All Posts

How to add dark mode in Tailwind CSS

Dark mode isn't just a trend anymore, it's something users expect. If your website doesn't support it, you're probably annoying a good chunk of your visitors who prefer darker interfaces at night or just in general.

The good news? Tailwind CSS makes adding dark mode pretty easy once you understand the two main approaches and know which one fits your project better.

Let me walk you through everything you need to know to get dark mode working in your Tailwind projects.

The two dark mode strategies

Tailwind gives you two ways to handle dark mode, and picking the right one matters.

The first is the media query strategy. This uses the prefers-color-scheme CSS media feature to automatically detect if someone has dark mode enabled on their operating system or browser. Zero JavaScript needed. The site just adapts.

The second is the class strategy. With this approach, you control dark mode manually by adding or removing a dark class on your HTML element. This gives you full control and lets you build a toggle button so users can switch themes regardless of their system settings.

Most developers prefer the class strategy because it gives users choice. Some people have their OS set to light mode but still want dark mode on certain websites. The class strategy lets you build that experience.

```html

Content adapts automatically
Content changes when .dark class is present
```

The class strategy is way more popular in production apps because you can save the user's preference and build proper UI controls for it.

Setting up class-based dark mode

Getting class-based dark mode running depends on which version of Tailwind you're using. The setup is slightly different between v3 and v4.

For Tailwind CSS v3, you need to modify your tailwind.config.js file:

```javascript
// tailwind.config.js
module.exports = {
  darkMode: 'class',
  theme: {
    // your theme config
  },
  plugins: [],
}
```

That one line darkMode: 'class' is all you need in v3.

For Tailwind CSS v4, the approach changed. You now use the @custom-variant directive in your main CSS file:

```css
@import "tailwindcss";

@custom-variant dark (&:where(.dark, .dark *));

</code></pre>
</div>

The v4 approach is more flexible and follows CSS standards better. You can even customize the selector if you want to use something other than `.dark`. Once you've got this set up, using dark mode styles is the same in both versions. Just prefix any utility class with `dark:`: <div class="sr-only"> <pre><code class="language-html">```html <div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-white p-6 rounded-lg"> <h2 class="text-2xl font-bold mb-4">This adapts to dark mode</h2> <p class="text-gray-600 dark:text-gray-300">When the .dark class is on the html element, this text turns light gray.</p> <button class="bg-blue-500 dark:bg-blue-600 hover:bg-blue-600 dark:hover:bg-blue-700 text-white px-4 py-2 rounded"> Click me </button> </div>

You can add dark: to pretty much any Tailwind utility. It works with backgrounds, borders, text colors, shadows, and even pseudo-classes like dark:hover:bg-blue-700.

Building a dark mode toggle with JavaScript

Having dark mode support is great, but users need a way to actually switch between themes. This is where JavaScript comes in.

The most important thing is preventing the flash of unstyled content (FOUC) – that annoying flicker when the page loads with the wrong theme before JavaScript kicks in. To fix this, you need to add a small inline script in your HTML <head> that runs before the page renders:

```html



  
  


  


```

This script checks localStorage first, then falls back to system preferences if no saved preference exists.

Now for the toggle button itself. Here's a complete implementation:

```html


</code></pre>
</div>

The toggle button switches the icon and saves your preference to localStorage. When you come back to the site, it remembers your choice. ## Common dark mode patterns and best practices Now that you've got dark mode working, let's talk about doing it well. There are some patterns that make dark mode feel professional instead of just "inverted colors." **Don't use pure black.** Instead of `bg-black`, use something like `bg-gray-900` or `bg-slate-900`. Pure black (#000000) creates too much contrast and can be harsh on the eyes, especially on OLED screens. Slightly lighter shades feel way better. **Layer your dark backgrounds.** Use different shades to create depth. Your page background might be `bg-gray-900`, cards could be `bg-gray-800`, and elevated elements like modals could be `bg-gray-700`. This creates visual hierarchy without needing heavy shadows. <div class="sr-only"> <pre><code class="language-html">```html <!-- Good dark mode with layered backgrounds --> <div class="min-h-screen bg-gray-900"> <nav class="bg-gray-800 border-b border-gray-700"> <!-- navigation --> </nav> <main class="container mx-auto p-6"> <div class="bg-gray-800 rounded-lg p-6 shadow-lg"> <h2 class="text-white text-2xl mb-4">Card Title</h2> <p class="text-gray-300">Card content with good contrast</p> <button class="mt-4 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded"> Action Button </button> </div> </main> </div>

Text contrast matters for accessibility. Don't just use text-gray-500 everywhere in dark mode. Use text-gray-300 or text-gray-400 for body text to maintain proper contrast ratios. Headlines should be text-white or text-gray-100.

Here's a practical example showing good contrast choices:

```html

Article Title

Posted on November 10, 2025

This is the main body text. In dark mode, gray-300 provides excellent readability without being harsh.

Secondary information uses slightly dimmer colors.

```

The key is using lighter grays for text in dark mode than you might think you need. Test your contrast ratios to make sure they meet WCAG standards.

Advanced techniques and edge cases

Once you've got the basics down, there are a few advanced scenarios worth knowing about.

Using data attributes instead of classes. Some developers prefer data attributes for semantic reasons or to avoid conflicts with other class-based systems. With Tailwind v4, you can easily switch to this approach:

```css
@import "tailwindcss";

@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));

</code></pre>
</div>

Then in your JavaScript, instead of adding a class, you'd set the attribute: `document.documentElement.setAttribute('data-theme', 'dark')`. **Handling images in dark mode.** Images can look weird in dark mode if they're designed for light backgrounds. You have a few options here: <div class="sr-only"> <pre><code class="language-html">```html <!-- Option 1: Reduce opacity slightly --> <img src="logo.png" class="dark:opacity-90" alt="Logo"> <!-- Option 2: Add a subtle filter --> <img src="photo.jpg" class="dark:brightness-90 dark:contrast-90" alt="Photo"> <!-- Option 3: Swap images entirely --> <img src="logo-light.png" class="dark:hidden" alt="Logo"> <img src="logo-dark.png" class="hidden dark:block" alt="Logo">

The image swap technique works great for logos and graphics that were specifically designed for one theme or the other.

Using CSS variables for more control. If you need really flexible theming beyond just light and dark, CSS variables give you more power:

```css
@layer theme {
  :root {
    --color-primary: #3b82f6;
    --color-background: #ffffff;
    --color-text: #111827;
  }

.dark {
--color-primary: #60a5fa;
--color-background: #111827;
--color-text: #f9fafb;
}
}

/* Then use in your HTML */

Content
```

This approach is especially useful if you're building an app that needs multiple theme options, not just light and dark.


That's everything you need to know about implementing dark mode in Tailwind CSS. Start with the class strategy for flexibility, build a solid toggle with localStorage persistence, follow the design best practices for colors and contrast, and you'll have a dark mode implementation that your users actually want to use.

The most important thing? Test it in real usage. Use your site in dark mode yourself for a few days and you'll quickly spot any issues with contrast, colors, or readability.

Other posts you might like...

See more

Subscribe to our newsletter

Get notified when we add new templates, components, and more!

Thanks for subscribing!
Please check your email to confirm your subscription.

WindyBase Make development a breeze

WindyBase is a weekly curated Tailwind CSS template and tool directory built for the modern developer.

© 2025 WindyBase. All rights reserved.