Customizing Tailwind: Theme and Design Tokens
Extend Tailwind's theme with custom colors, spacing, and design tokens that scale across a real product without fighting the framework.
What you'll learn
- ✓theme vs extend
- ✓Color scales
- ✓CSS variables
- ✓Dark mode tokens
- ✓Token naming
Prerequisites
- •Comfortable with JS
What and Why
Tailwind ships with a sensible default theme: a color palette, a spacing scale, a type ramp. Real products outgrow defaults quickly. You need brand colors, custom radii, perhaps a denser spacing scale for tables. The goal is to extend Tailwind so your design system lives in one place and every utility class reflects it.
Design tokens are the bridge between Figma and code. When designers change a brand color, you change it once in tailwind.config.js (or in CSS variables) and every component updates. No find-and-replace, no drift.
Mental Model
Tailwind’s theme is a tree. theme.colors, theme.spacing, theme.fontSize, and so on. Replacing a branch wipes the defaults; extending it merges with them. Most teams want to extend for additive tokens (a new brand color) and replace for opinionated overrides (a fully custom spacing scale).
tailwind.config.js
theme.extend.colors.brand = { 500: 'oklch(...)' }
|
v
class="bg-brand-500"
|
v
background-color: oklch(...); Hands-on Example
Add brand colors and a custom radius:
// tailwind.config.js
export default {
content: ['./src/**/*.{astro,html,js,ts,jsx,tsx}'],
theme: {
extend: {
colors: {
brand: {
50: 'oklch(97% 0.02 270)',
500: 'oklch(60% 0.18 270)',
900: 'oklch(25% 0.10 270)',
},
},
borderRadius: { xl2: '1.25rem' },
fontFamily: { display: ['Inter Variable', 'sans-serif'] },
},
},
};
Use them like any utility:
<button class="bg-brand-500 text-white rounded-xl2 font-display">
Continue
</button>
For runtime theming (light, dark, branded tenants), drive tokens through CSS variables:
:root {
--color-bg: oklch(99% 0 0);
--color-fg: oklch(15% 0 0);
}
.dark {
--color-bg: oklch(15% 0 0);
--color-fg: oklch(95% 0 0);
}
// tailwind.config.js
extend: {
colors: {
bg: 'var(--color-bg)',
fg: 'var(--color-fg)',
},
}
Now bg-bg text-fg flips on .dark without rebuilding.
Common Pitfalls
A few common traps:
- Replacing instead of extending. Writing
theme: { colors: { brand: ... } }deletes every default color. Usetheme.extend. - Hex everywhere. Modern color spaces like
oklchgive perceptually even ramps and richer gradients. Hex is fine but limits you. - Magic numbers in markup.
mt-[17px]defeats the system. Add a token if you need a specific value more than once. - Forgetting safelist. Classes built from dynamic strings (
bg-${color}-500) get purged. Use safelist or precompute the full class. - Per-component custom CSS. If you find yourself opening a
.cssfile, ask whether a token is missing.
Best Practices
Name tokens by role, not by appearance. bg-surface survives a redesign; bg-slate-50 does not. Keep two layers: primitive tokens (color scales, raw spacing) and semantic tokens (surface, border, accent) that reference primitives. Components consume the semantic layer.
Limit the palette. Five or six colors with three to five steps each cover almost any app. Bigger palettes invite inconsistency. The same applies to spacing: a tight scale forces visual rhythm.
Document tokens where designers can find them. A simple page that renders every color and spacing utility keeps everyone aligned and surfaces gaps early.
Wrap-up
Extend Tailwind’s theme thoughtfully, lean on CSS variables for runtime theming, and name tokens by role. Done well, your tailwind.config.js becomes the single source of truth for visual language, and every component class reads like the design system speaking.
Related articles
- Tailwind Tailwind Dark Mode Strategies: Class, Media, and CSS Variables
Compare the class-based, media-based, and variable-driven approaches to dark mode in Tailwind, with code and the trade-offs of each.
- Tailwind Tailwind Animation and Transition Utilities
Animate hover states, page mounts, and component transitions with Tailwind's transition and animation utilities and a few custom keyframes.
- Tailwind Tailwind Arbitrary Values and the JIT Engine
How Tailwind's JIT engine generates classes on demand, when arbitrary values are the right tool, and how to keep your design system tidy.
- Tailwind Tailwind Custom Plugins Tutorial
Write your own Tailwind plugins to add utilities, components, and variants that match your design system without reaching for arbitrary values everywhere.