Tailwind Colors, Dark Mode, and Theme Customization
How to work with Tailwind's color palette — opacity modifiers, arbitrary values, CSS variables for theming, the dark: variant, and configuring tokens with @theme in Tailwind v4.
What you'll learn
- ✓How Tailwind names colors and shades
- ✓Opacity modifiers like bg-emerald-500/80
- ✓When to use arbitrary values like text-[#1f2937]
- ✓CSS variables for theming and runtime customization
- ✓The dark: variant and the strategies behind it
- ✓Configuring tokens in Tailwind v4 with @theme
Prerequisites
Tailwind ships with a thoughtfully designed color system: 22 base hues, each with 11 shades, all selected to work well together. Most of the time you can pick from the palette and ship. When you cannot, Tailwind gives you escape hatches — opacity modifiers, arbitrary values, and CSS variables — so you are never stuck.
This post is the practical guide to using colors well in Tailwind.
The default palette
Every color follows the pattern {hue}-{shade}:
<div class="bg-slate-50">Almost white</div>
<div class="bg-slate-500">Mid grey</div>
<div class="bg-slate-900">Almost black</div>
<p class="text-emerald-600">Success green</p>
<p class="text-red-600">Danger red</p>
<p class="text-amber-500">Warning amber</p>
The shades run from 50 (almost white) through 500 (the saturated mid-tone) to 950 (almost black). The shade numbers are consistent across hues, so bg-blue-600 and bg-emerald-600 have similar perceived brightness.
The hues you reach for most:
- Greys:
slate,gray,zinc,neutral,stone— pick one and stick to it across the project. - Brand-ish blues:
blue,sky,indigo,cyan. - Success:
green,emerald,teal. - Warning:
yellow,amber,orange. - Danger:
red,rose,pink.
For body text on a white background, text-slate-700 or text-slate-800 reads cleanly. For muted text, text-slate-500 is a good starting point.
Opacity modifiers
Tailwind’s slash syntax lets you set an alpha channel on any color without writing custom CSS:
<!-- 80% opacity emerald -->
<div class="bg-emerald-500/80">…</div>
<!-- 20% opacity for a subtle tint -->
<div class="bg-blue-500/20 text-blue-800">Info banner</div>
<!-- Translucent border -->
<div class="border border-slate-900/10">…</div>
The number after the slash is a percentage. Any value from 0 through 100 works. This is the cleanest way to build tinted backgrounds, glass effects, and hover overlays.
<!-- Glass card on a colorful background -->
<div class="bg-white/70 backdrop-blur p-6 rounded-xl">
…
</div>
Arbitrary values
When the palette does not have what you need — a brand color from a designer, a one-off accent — wrap the value in square brackets:
<div class="bg-[#1d4ed8] text-[#f9fafb]">…</div>
<!-- Arbitrary works for any utility -->
<div class="w-[417px] mt-[7px] grid-cols-[200px_1fr]">…</div>
Arbitrary values are an escape hatch, not a default. If you find yourself writing the same arbitrary color across many files, it belongs in your theme as a named token instead.
Try it. Open a component on your site and replace one hardcoded hex color with the closest Tailwind palette equivalent. Does the design suffer? Usually it does not — and the consistency improves.
CSS variables for theming
Tailwind v4 is built on CSS custom properties. Every color in the palette is a variable like --color-emerald-500. That means you can reference colors directly in your own CSS, override them per scope, or layer entire themes.
:root {
--color-brand: #2563eb;
--color-brand-hover: #1d4ed8;
}
[data-theme="sunset"] {
--color-brand: #ea580c;
--color-brand-hover: #c2410c;
}
<button class="bg-[var(--color-brand)] hover:bg-[var(--color-brand-hover)]">
Save
</button>
Toggle data-theme on the <html> element and every component re-themes instantly. No JavaScript rebuild, no class swap on every button.
Dark mode: the dark: variant
Tailwind’s dark: variant lets you write a light styling and then say “but in dark mode, use this instead”:
<article class="bg-white text-slate-900 dark:bg-slate-900 dark:text-slate-100">
<h2 class="text-slate-800 dark:text-slate-200">Title</h2>
</article>
By default, dark: follows the user’s OS preference via prefers-color-scheme: dark. If you want a manual toggle, configure Tailwind v4 to use a class or data attribute:
/* app.css */
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
Now dark: activates whenever an ancestor has the dark class. A button at the top of your site can toggle that class on <html> and the whole page switches.
document.documentElement.classList.toggle("dark");
To remember the choice across reloads, persist it in localStorage and apply it before the first paint to avoid a flash.
Dark mode tips
A few rules of thumb learned the hard way:
- Do not just invert. Pure black on pure white is harsh in dark mode. Use
slate-900orzinc-900for the background andslate-100for primary text. Save pure black for the OLED-specific themes if you really want it. - Reduce saturation. Bright brand colors that look great on white can feel radioactive on dark. Use a slightly desaturated or darker shade —
blue-500in light,blue-400in dark, for example. - Borders need help. A
border-slate-200border disappears on a dark background. Usedark:border-slate-700or rely on subtle background contrast instead. - Images need attention. Logos with transparency around dark text become invisible. Ship an alternate asset or wrap in a light card.
<button class="bg-blue-600 text-white hover:bg-blue-700
dark:bg-blue-500 dark:hover:bg-blue-400">
Save
</button>
Configuring tokens with @theme
In Tailwind v4, you customize the design system from CSS, not from a JavaScript config file. The @theme block defines tokens that become utility classes.
/* app.css */
@import "tailwindcss";
@theme {
--color-brand-50: #eff6ff;
--color-brand-500: #2563eb;
--color-brand-600: #1d4ed8;
--color-brand-900: #1e3a8a;
--font-display: "Cal Sans", "Inter", sans-serif;
--spacing-128: 32rem;
--breakpoint-3xl: 1920px;
}
Those tokens automatically generate utilities:
<div class="bg-brand-500 text-white font-display">
CodeLoom
</div>
<section class="max-w-128 mx-auto 3xl:px-12">…</section>
A few patterns:
- Brand colors as
--color-brand-{shade}give you the full Tailwind ergonomics for your custom hue. - Fonts under
--font-{name}createfont-{name}utilities. - Spacing under
--spacing-{key}gives youp-{key},m-{key},gap-{key}, etc. - Breakpoints under
--breakpoint-{name}give you{name}:responsive variants.
Putting it together: a themed button
A real-world example combining most of what we have seen:
@theme {
--color-brand-500: #2563eb;
--color-brand-600: #1d4ed8;
}
[data-theme="sunset"] {
--color-brand-500: #ea580c;
--color-brand-600: #c2410c;
}
<button class="bg-brand-500 hover:bg-brand-600
text-white font-medium
px-4 py-2 rounded-md
transition-colors
dark:bg-brand-600 dark:hover:bg-brand-500
disabled:opacity-50 disabled:cursor-not-allowed">
Save changes
</button>
The button:
- Uses brand tokens you control from one place.
- Re-themes via
[data-theme="sunset"]without changing classes. - Has a dark mode variant with swapped shades.
- Includes a disabled state via the
disabled:variant.
Try it. Add a --color-brand-500 to your @theme block and replace bg-blue-600 somewhere in your project with bg-brand-500. The visual is identical, but now the color lives in one place.
A quick decision tree
When you need a color, ask:
- Is it one of the standard palette colors? Use it directly:
bg-slate-700. - Is it a palette color with transparency? Use the slash modifier:
bg-slate-700/60. - Is it a brand or product-specific color? Define a token in
@theme:bg-brand-500. - Is it a true one-off? Use an arbitrary value:
bg-[#abc123].
The order matters. Palette first, tokens for reuse, arbitrary as a last resort.
Recap
- The palette is
{hue}-{shade}from50to950. Pick a grey and stick with it. - Opacity modifiers (
bg-color/80) handle tints and overlays without custom CSS. - Arbitrary values (
bg-[#hex]) cover one-off cases. - CSS variables and
[data-theme]enable multi-theme layouts. dark:adds dark mode styling — do not just invert, restyle thoughtfully.@themein v4 defines your tokens directly in CSS, generating utilities automatically.
Next steps
- Read Tailwind Layout: Flex, Grid, Spacing, Sizing to pair colors with layout.
- Read Tailwind Component Patterns for reusable themed components.
- Revisit CSS Animations and Transitions —
transition-colorsis what makes button hovers feel right.
Questions or feedback? Email codeloomdevv@gmail.com.