seedflip
Archive
Mixtapes
Pricing
Sign in

Tailwind v4 Color System: What Changed and How to Migrate

Tailwind v4 replaced the JavaScript config file with CSS-first configuration, adopted OKLCH as its default color space, and redesigned its entire default palette with perceptually uniform lightness steps. If you're migrating from v3, your custom colors still work. But understanding what changed helps you build better systems going forward.

Generate a Tailwind v4 color system →

The big three changes

Tailwind v4 changed three things about colors that matter to your daily workflow. Everything else is implementation details.

1. CSS-first configuration

In v3, you defined custom colors in tailwind.config.js (or .ts). JavaScript object, nested keys, hex strings. In v4, configuration moves to your CSS file using @theme:

/* Tailwind v3: tailwind.config.js */ module.exports = { theme: { extend: { colors: { brand: '#6366F1', surface: '#FAFAFA', }, }, }, } /* Tailwind v4: your CSS file */ @theme { --color-brand: #6366F1; --color-surface: #FAFAFA; }

Same result, different location. The utility classes bg-brand and bg-surface work identically. But now your color definitions live in CSS alongside your other custom properties, which means they can reference other CSS variables, use calc(), and participate in the cascade.

2. OKLCH color space

Tailwind v4's default palette is defined in OKLCH instead of hex or HSL. OKLCH is a perceptually uniform color space. That's a fancy way of saying: blue-500 and yellow-500 actually look like the same lightness to the human eye.

In the old HSL-based palette, blue-500 and yellow-500 had the same lightness value (50%) but looked wildly different in brightness. Yellow appeared much brighter because HSL doesn't account for how our eyes perceive different hues. OKLCH fixes this.

/* HSL: same "lightness" but different perceived brightness */ --blue-500: hsl(217 91% 60%); /* looks medium */ --yellow-500: hsl(48 96% 53%); /* looks bright */ /* OKLCH: same lightness that ACTUALLY looks the same */ --blue-500: oklch(0.623 0.214 259); --yellow-500: oklch(0.623 0.178 84);

For you, this means the default palette is more consistent out of the box. Switching between color scales (from blue to green, for example) produces less visual surprise. And if you build your own scales programmatically, OKLCH makes it easier to generate even lightness ramps.

3. Redesigned default palette

The default colors aren't just converted from v3 to OKLCH. They're redesigned. The lightness steps are recalibrated, some hues shifted slightly, and the neutral grays gained a bit more warmth in the middle ranges.

If you hardcoded specific hex values from the v3 palette into your designs (screenshots, Figma files, brand guidelines), those exact values won't match the v4 defaults. The classes (bg-blue-500) work the same way, but the rendered color is slightly different.

What this means for your migration

If you use Tailwind's default colors directly

Your bg-blue-500 classes still work. They just resolve to the new OKLCH value instead of the old hex. The visual difference is subtle in most cases. Unless you have pixel-perfect brand requirements tied to the exact v3 hex values, you probably won't notice.

If you use custom colors in tailwind.config.js

Your custom hex colors work exactly as before. Hex is valid in @theme. You don't have to convert to OKLCH. The migration path for custom colors is mechanical: move the values from the JS config to the CSS @theme block.

/* v3 config → v4 CSS. Your hex values stay the same. */ @theme { --color-primary: #6366F1; --color-primary-hover: #4F46E5; --color-surface: #FAFAFA; --color-border: #E2E8F0; }

If you use CSS variables in your config

This is where v4 is actually simpler. In v3, referencing CSS variables in your Tailwind config was awkward. You had to use the var() wrapper and lose opacity modifier support, or define colors without the function wrapper. In v4, CSS variables and @theme live in the same file, so cross-referencing is natural:

:root { --brand-indigo: #6366F1; --brand-slate: #0F172A; } @theme { --color-primary: var(--brand-indigo); --color-background: var(--brand-slate); }

Building new color scales in OKLCH

If you're starting a new project on v4 (not migrating), consider defining custom scales in OKLCH for consistency with the default palette. The pattern is straightforward: keep the hue and chroma roughly constant, and vary the lightness in even steps.

@theme { /* Custom indigo scale in OKLCH */ --color-indigo-50: oklch(0.97 0.02 277); --color-indigo-100: oklch(0.93 0.04 277); --color-indigo-200: oklch(0.87 0.08 277); --color-indigo-300: oklch(0.77 0.13 277); --color-indigo-400: oklch(0.67 0.18 277); --color-indigo-500: oklch(0.59 0.21 277); --color-indigo-600: oklch(0.50 0.20 277); --color-indigo-700: oklch(0.43 0.17 277); --color-indigo-800: oklch(0.36 0.14 277); --color-indigo-900: oklch(0.28 0.10 277); --color-indigo-950: oklch(0.20 0.07 277); }

The lightness values step evenly from 0.97 (near-white) to 0.20 (near-black). The chroma peaks in the middle (where colors are most vivid) and tapers at the extremes (where colors become almost neutral). This mirrors how the default palette works and gives you a scale that feels native to v4.

The practical migration checklist

1. Move custom colors from tailwind.config.js to @theme in your CSS file. Keep the hex values as-is.

2. If you relied on specific v3 default hex values (in Figma, brand docs, or hardcoded strings), note that they've changed. Update your reference docs.

3. If you used the theme() function in your CSS, it still works in v4 but the recommended pattern is direct CSS variable references.

4. If you used @apply with color utilities, those work the same way. The class names didn't change.

5. Test your dark mode. If your dark theme used hardcoded v3 palette hex values, they'll still work but might look slightly different next to components using the new v4 defaults.


The migration is more organizational than technical. Your colors don't break. They move from JavaScript to CSS and you get a better default palette in the process. For ideas on what to do with the new color system, check Tailwind Color Palette Ideas. To build a scalable system on top of it, read How to Build a Tailwind Color System That Scales. For the CSS variable layer underneath, see CSS Variables Color System.

Ready to stop guessing?

One flip. Complete design system. Free CSS export.

Generate a Tailwind v4 color system →