This is the last part of the Tailwind CSS series. We have a card, a grid, a highlighted pricing card and working buttons. Now we will give the page a second face, a dark theme, and a single place to change the brand colour. Then we will wrap up what we built and where to go next.

Dark mode is the part of this series I most often see done badly. The usual mistake is to write a second stylesheet that has to be kept in step with the first. Tailwind avoids that: each colour gets a light value and a dark: partner, side by side in the markup. You can see both at once, and nothing drifts apart.

Pair every light colour with a dark one

Take the card from part 2 and give each colour a dark partner. The light value applies by default, and the dark: value applies when dark mode is on:

<article class="max-w-sm rounded-xl bg-white p-6 shadow-md dark:bg-slate-900 dark:ring-1 dark:ring-slate-700">
  <h2 class="text-xl font-semibold text-slate-900 dark:text-slate-100">Utility classes</h2>
  <p class="mt-3 leading-relaxed text-slate-600 dark:text-slate-400">Small single-purpose classes you combine in the markup.</p>
</article>

Notice dark:ring-1. In dark mode a white card’s shadow almost disappears against a dark background, so a thin border keeps the edge visible. That is the kind of detail that is easy to miss until you look at the page in both modes.

Choose: follow the system, or let the user toggle

By default, dark: follows the operating system through the prefers-color-scheme setting, so you get dark mode with no code at all. If you want a switch the visitor controls, change the variant so it reads a class on the page instead. In Tailwind v4 that is one line in your stylesheet, not a config file:

/* src/style.css */
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));

With that line in place, dark: applies whenever an ancestor has the dark class. Add it to the root element with a small script. Keep the script in the document head so the page does not flash the wrong theme before it loads:

<script>
  document.documentElement.classList.toggle(
    "dark",
    localStorage.theme === "dark" ||
      (!("theme" in localStorage) &&
        window.matchMedia("(prefers-color-scheme: dark)").matches)
  );
</script>

One brand colour, defined once

Hard-coding teal-600 across forty classes works until the client asks for a different shade. The Tailwind v4 way is to declare your own colours in a @theme block. Each variable starting with --color- becomes a utility class, so the brand colour can be changed in one place:

/* src/style.css */
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));

@theme {
  --color-brand-50: #ecfeff;
  --color-brand-600: #0891b2;
  --color-brand-700: #0e7490;
}

Now bg-brand-600, text-brand-700 and hover:bg-brand-700 all exist. Swap the hex values and the whole site follows. I tested this in a fresh Vite project, and the build generated each of those utilities from the theme variables.

What you built, and where to go next

Over five parts you set up Vite and Tailwind v4, learned to read a class list from left to right, built a responsive grid, styled buttons and a pricing card with real states, and added dark mode and a brand theme. Every piece lives in the markup and one stylesheet. There is no separate config file to keep in sync, and nothing you do not use ends up in the final CSS.

Good next steps are a component version of the card, a real navigation menu for small screens, and a form with validation styles. If you are deciding whether Tailwind suits a project at all, compare it with a component library first. Tailwind gives you full control; a library gives you a faster start. Pick the one that matches how much design work you want to own.

Keep going in the series

This series builds one project, so each part picks up where the last one stopped. Here are the other parts:

If you want a shorter read on a single topic first, these standalone guides cover the same ground:

Frequently asked questions

Do I need JavaScript for dark mode in Tailwind?

No, not for the basic version. dark: follows the system setting with no JavaScript. You only need a script if you want a visitor-controlled toggle that remembers the choice.

Why does my dark mode flash white when the page loads?

The theme is applied after the page renders. Put the small theme script in the document head, before the body, so the first paint already uses the right colours.

Where do I define brand colours in Tailwind v4?

In a @theme block in your main stylesheet. Each –color-* variable becomes a utility class, so –color-brand-600 gives you bg-brand-600 and text-brand-600.

Last updated on · Written by