Skip to content
C Codeloom
Tailwind

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.

·3 min read · By Codeloom
Intermediate 8 min read

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(...);
Token flow from config to utility class to rendered style

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. Use theme.extend.
  • Hex everywhere. Modern color spaces like oklch give 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 .css file, 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.