Designing Custom Tailwind Palettes
Why Custom Palettes Matter
Tailwind's default palette is excellent for prototyping, but production products need brand-specific colors. A custom palette ensures visual consistency across your entire application and makes your product recognizable. Rather than overriding Tailwind utilities ad hoc, define a complete palette that integrates with Tailwind's token system.
Tailwind Color Configuration
Basic Custom Colors
// tailwind.config.ts
export default {
theme: {
extend: {
colors: {
brand: {
50: '#f0f5ff',
100: '#e0ebff',
200: '#c2d6ff',
300: '#94b8ff',
400: '#6699ff',
500: '#3366ff',
600: '#1a4fff',
700: '#0033cc',
800: '#002699',
900: '#001a66',
950: '#000d33',
},
},
},
},
}
This gives you bg-brand-500, text-brand-700, border-brand-200, and every other Tailwind color utility — automatically.
Multi-Brand Palettes
For products with distinct color families:
colors: {
brand: { /* primary blue */ },
accent: { /* secondary orange */ },
surface: { /* neutral backgrounds */ },
feedback: {
success: '#16a34a',
warning: '#d97706',
error: '#dc2626',
info: '#2563eb',
},
}
Generating Shade Scales
The hardest part of custom palettes is generating consistent shade scales. A good scale progresses smoothly from very light to very dark while maintaining the same hue identity.
The OKLCH Approach
OKLCH is a perceptually uniform color space — equal numeric steps produce equal visual steps. This makes it ideal for generating shade scales:
/* Define your brand color in oklch */
:root {
--brand-500: oklch(0.55 0.2 260);
}
From a single oklch anchor, you can derive a full scale by adjusting lightness while keeping chroma and hue constant:
| Shade | Lightness | Result |
|---|---|---|
| 50 | 0.97 | Very light tint |
| 100 | 0.93 | Light tint |
| 200 | 0.85 | Soft shade |
| 300 | 0.75 | Medium-light |
| 400 | 0.65 | Medium |
| 500 | 0.55 | Base color |
| 600 | 0.45 | Medium-dark |
| 700 | 0.37 | Dark |
| 800 | 0.28 | Very dark |
| 900 | 0.22 | Near black |
| 950 | 0.15 | Deepest shade |
// Generate shades from an oklch base
function generateShades(L, C, H) {
const lightnesses = [0.97, 0.93, 0.85, 0.75, 0.65, 0.55, 0.45, 0.37, 0.28, 0.22, 0.15]
return lightnesses.map(l => `oklch(${l} ${C} ${H})`)
}
Hex Fallbacks
OKLCH browser support is growing but not universal. Generate hex values for your Tailwind config and deliver oklch via CSS custom properties for progressive enhancement:
:root {
--brand-500: #3366ff; /* Fallback */
--brand-500: oklch(0.55 0.2 260); /* Modern browsers override */
}
Design Token Architecture
CSS Custom Properties as Source of Truth
/* app/assets/css/main.css */
:root {
/* Brand palette */
--color-brand-50: oklch(0.97 0.02 260);
--color-brand-500: oklch(0.55 0.2 260);
--color-brand-900: oklch(0.22 0.15 260);
/* Semantic tokens */
--color-bg-primary: var(--color-brand-50);
--color-text-primary: var(--color-brand-900);
--color-interactive: var(--color-brand-500);
}
.dark {
--color-bg-primary: var(--color-brand-950);
--color-text-primary: var(--color-brand-50);
--color-interactive: var(--color-brand-400);
}
Mapping Tokens to Tailwind
// tailwind.config.ts
export default {
theme: {
extend: {
colors: {
bg: {
primary: 'var(--color-bg-primary)',
},
text: {
primary: 'var(--color-text-primary)',
},
interactive: 'var(--color-interactive)',
brand: {
50: 'var(--color-brand-50)',
500: 'var(--color-brand-500)',
900: 'var(--color-brand-900)',
},
},
},
},
}
Dark Mode Palette Design
Dark mode is not just inverted light mode. Key principles:
- Don't switch to pure black — use your darkest brand shade or a dark neutral like
slate-900 - Reduce chroma — saturated colors appear to vibrate on dark backgrounds. Drop chroma by 15–20% in dark mode
- Increase lightness gap — on dark backgrounds, you need more contrast between text and surface than on light backgrounds
- Test both themes simultaneously — design components that look correct in both modes
Validation Checklist
Before shipping a custom palette:
- Every text color meets 4.5:1 contrast against its background (both themes)
- The scale steps are perceptually even (no jarring jumps)
- Brand colors are distinguishable under color vision deficiency simulation
- Dark mode palette doesn't cause eye strain (no saturated-on-dark combinations)
- All shades are used — if 100 and 200 are too similar, collapse them
- CSS custom properties and Tailwind config values stay in sync
Related Guides
Try It Yourself
Design and preview custom color palettes with live shade generation. Export directly to Tailwind config or CSS custom properties.