Tailwind ships with a carefully designed default theme, but real projects need custom branding. Tailwind makes it straightforward to extend or override every design token.
Tailwind CSS 4: Theme Configuration
In Tailwind CSS 4, theme customization is done directly in your CSS using the @theme directive instead of a JavaScript config file:
/* app/globals.css */
@import "tailwindcss";
@theme {
--color-brand: #E21B1B;
--color-brand-light: #FF4D4D;
--color-brand-dark: #B01515;
--color-surface: #FFFFFF;
--color-surface-alt: #F8F9FA;
--font-sans: "Ubuntu", ui-sans-serif, system-ui, sans-serif;
--font-mono: "JetBrains Mono", ui-monospace, monospace;
--font-serif: "Source Serif 4", ui-serif, Georgia, serif;
--spacing-18: 4.5rem;
--spacing-128: 32rem;
--radius-card: 0.75rem;
}These tokens immediately generate utilities like bg-brand, text-brand-light, font-sans, rounded-card, and w-128.
Custom Color Palette
Define a full color scale for your brand:
@theme {
--color-brand-50: #FEF2F2;
--color-brand-100: #FEE2E2;
--color-brand-200: #FECACA;
--color-brand-300: #FCA5A5;
--color-brand-400: #F87171;
--color-brand-500: #E21B1B;
--color-brand-600: #DC2626;
--color-brand-700: #B91C1C;
--color-brand-800: #991B1B;
--color-brand-900: #7F1D1D;
--color-brand-950: #450A0A;
}Now use these like any built-in color:
<button class="bg-brand-500 hover:bg-brand-600 text-white px-4 py-2 rounded">
Subscribe
</button>
<div class="border border-brand-200 bg-brand-50 text-brand-800 p-4 rounded-lg">
<p>Your trial expires in 3 days.</p>
</div>Custom Fonts
Register font families in @theme and load them with @font-face or a service like Google Fonts:
@theme {
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
--font-display: "Cal Sans", "Inter", sans-serif;
}<h1 class="font-display text-4xl font-bold">Welcome Back</h1>
<p class="font-sans text-gray-600">Here is your dashboard overview.</p>Dark Mode
Tailwind supports dark mode via a CSS variant. In Tailwind CSS 4, configure it with @custom-variant:
@custom-variant dark (&:where(.dark, .dark *));This makes the dark: variant apply when the dark class is on an ancestor element (typically <html>).
Toggling the Dark Class
function ThemeToggle() {
const toggle = () => {
const isDark = document.documentElement.classList.toggle("dark");
localStorage.setItem("theme", isDark ? "dark" : "light");
};
return (
<button onClick={toggle} class="p-2 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700">
<span class="dark:hidden">Moon Icon</span>
<span class="hidden dark:inline">Sun Icon</span>
</button>
);
}Preventing Flash of Wrong Theme
Add a blocking script in <head> that reads the stored preference before the page renders:
<script>
if (
localStorage.theme === "dark" ||
(!("theme" in localStorage) && window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
document.documentElement.classList.add("dark");
}
</script>Styling for Dark Mode
Apply dark: variants alongside your default styles:
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 min-h-screen">
<div class="max-w-xl mx-auto p-8">
<h1 class="text-2xl font-bold">Settings</h1>
<div class="mt-6 bg-gray-50 dark:bg-gray-800 rounded-lg p-6 border border-gray-200 dark:border-gray-700">
<label class="flex items-center justify-between">
<span class="text-gray-700 dark:text-gray-300">Email notifications</span>
<input type="checkbox" class="rounded border-gray-300 dark:border-gray-600" />
</label>
</div>
</div>
</div>CSS Variables for Dynamic Theming
CSS variables let you change themes at runtime without recompiling CSS. Define variables and reference them in @theme:
:root {
--ui-bg: #FFFFFF;
--ui-text: #111827;
--ui-accent: #2563EB;
}
.dark {
--ui-bg: #0F172A;
--ui-text: #F1F5F9;
--ui-accent: #3B82F6;
}
@theme {
--color-ui-bg: var(--ui-bg);
--color-ui-text: var(--ui-text);
--color-ui-accent: var(--ui-accent);
}<div class="bg-ui-bg text-ui-text">
<a href="#" class="text-ui-accent hover:underline">Learn more</a>
</div>This technique also works for multi-brand theming — swap variables based on a class or data attribute:
[data-brand="acme"] {
--ui-accent: #7C3AED;
}
[data-brand="globex"] {
--ui-accent: #059669;
}<body data-brand="acme">
<button class="bg-ui-accent text-white px-4 py-2 rounded">
Brand-specific button
</button>
</body>