f in x
> cd .. / HUB_EDITORIALE
Analisi dei dati e metriche

Dark Mode with Tailwind: Class Strategy and Practical Implementation

[2026-06-11] Author: Meteora Web Redazione

Have you ever added dark mode to a site and ended up with duplicate Tailwind classes for every color? Or worse, used JavaScript to change styles at runtime, turning your CSS into a maintenance nightmare? You're not alone. We see it often in projects that come to us: dark mode implemented in a hacky way that multiplies technical debt.

We, at Meteora Web, have dealt with this problem dozens of times. On e-commerce sites, brochure sites, SaaS platforms. And we learned that the cleanest, most maintainable, and performant solution is Tailwind's class strategy. In this guide we'll explain why it works and how to put it into practice, starting from the real problem.

Why the Class Strategy Beats Any Other Solution

When you add a dark theme, you have two classic routes: write custom CSS for each color, or use CSS variables and change them via JavaScript. Both work, but both have a maintenance cost that grows with the project.

With Tailwind and the dark: directive you get the best of both: utility classes stay in the markup, you don't write extra CSS, and the theme toggle is handled by a single selector on the root element. The browser automatically applies dark variants when the parent has the class dark.

Real advantage: zero maintenance

Suppose you have a button with light blue background and white text in light mode, and dark blue background with gray text in dark mode. With the class strategy you write:

<button class="bg-blue-500 dark:bg-blue-800 text-white dark:text-gray-300">Click me</button>

That's it. You don't need to hunt for @media (prefers-color-scheme: dark) in two different stylesheets. All the visual logic lives in the component. If you change your mind about colors tomorrow, you edit a single line.

Configuration: darkMode: 'class'

First open tailwind.config.js and set dark mode to “class” (instead of the default “media”). Beware: if you use “media”, Tailwind follows the OS preference automatically, but you can't give the user a manual choice. We always recommend “class” for full control.

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

A common mistake: forgetting to restart the dev server after changing this option. Tailwind won't regenerate the variants until the process restarts.

Practical implementation of the toggle

Now you need to decide where to apply the dark class. Usually you put it on the <html> or <body>. We prefer <html> because it's more radical and works for all elements inside.

Saving the preference in localStorage

Users expect their choice to be remembered across visits. Use JavaScript to read and write localStorage. Here's a minimal script to place in the <head> (before any other code) to avoid the flash of the wrong theme:

<script>
  const theme = localStorage.getItem('theme') ||
    (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
  if (theme === 'dark') {
    document.documentElement.classList.add('dark');
  }
</script>

Warning: this script must be synchronous and inline, otherwise you'll see a flash of the light theme before the DOM is updated. An e-commerce client with heavy images asked us to solve exactly that: a white flash ruining the experience on mobile with slow connection. This inline script eliminated it.

Toggle button with Vue, React, or vanilla HTML

The mechanism is always the same: when the user clicks, toggle the dark class on HTML and update localStorage. Here's a vanilla example:

<button id="theme-toggle" aria-label="Toggle theme">🌙</button>
<script>
  const toggle = document.getElementById('theme-toggle');
  toggle.addEventListener('click', () => {
    const html = document.documentElement;
    const isDark = html.classList.toggle('dark');
    localStorage.setItem('theme', isDark ? 'dark' : 'light');
  });
</script>

If you use React, the logic is identical: manage state with useState and apply the class on <html> with a side effect (useEffect). In Vue 3, a simple watch on the reactive variable does the job.

Custom styles for dark mode

Tailwind already includes many dark: variants for default colors. But if you need to customize specific shades (e.g., a bright red that should become muted in dark mode), declare them in tailwind.config.js under darkMode or simply use classes like dark:bg-red-900.

For non-color elements (borders, shadows, gradients)

The dark: prefix works for these too. Example for a border that is light gray in light mode and dark gray in dark mode:

<div class="border border-gray-300 dark:border-gray-700">...</div>

A common mistake: forgetting hover, focus, and active states. Apply those too:

<a href="#" class="text-gray-700 hover:text-blue-600 dark:text-gray-300 dark:hover:text-blue-400">Link</a>

Handling theme based on system preference (media option)

If for some reason you don't want to give the user a manual choice (e.g., an informational app with no login), you can use darkMode: 'media'. In this case Tailwind automatically applies dark variants when the OS is in dark mode. No JavaScript needed, but you end up with a less flexible experience.

We advise against this path for sites that require explicit customization. A user navigating in light mode during the day and dark mode at night expects to decide, not to be forced by the system.

Real cases: mistakes we've seen (and fixed)

  • Missing dark classes on key components. A checkout form in an e-commerce had a “Buy” button with white background even in dark mode. Result: text was invisible. Always check every visible component, not just the layout.
  • JavaScript adding the class after render. If you run the script at the bottom of the body, the browser loads the light CSS first, then overwrites it. This produces an annoying flash. Solution: inline script in <head> as shown above.
  • Insufficient testing on mobile. We've seen sites perfect on desktop but with illegible text on mobile due to poorly written media queries. Tailwind handles responsive variants separately, but make sure to test dark:sm:bg-gray-800 and similar.

In summary — what to do now

  1. Open your Tailwind project and set darkMode: 'class' in tailwind.config.js. Restart the dev server.
  2. Add the inline script in the <head> to read the saved theme or system preference.
  3. Create a toggle (button) that inverts the dark class on HTML and saves to localStorage.
  4. For every component that has colors, add the necessary dark: variants. Don't leave any color without its dark counterpart.
  5. Test on different browsers, with system theme light and dark, and with localStorage cleared. Also simulate theme changes mid-navigation.

This strategy is not just clean: it's fast too. No extra CSS, no heavy JavaScript. The browser applies variants directly through the cascade. And when the project grows, you'll thank yourself for keeping everything in a single utility layer.

We, at Meteora Web, have integrated this technique in more than ten projects, from small brochure sites to platforms with tens of thousands of users. It works. Now it's your turn.

Sponsored Protocol

Meteora Web Redazione

> AUTHOR_EXTRACTED

Meteora Web Redazione

La redazione di Meteora Web: notizie, analisi e aggiornamenti quotidiani dal mondo del digitale, dei social e dell'intelligenza artificiale.

[ Read Full Dossier ]

Hai bisogno di applicare questa strategia?

Esegui il protocollo di contatto per iniziare un progetto con noi.

> INIZIA_PROGETTO

Sponsored

> MW_JOURNAL

> READ_ALL()