Skip to content
100% in your browser. Nothing you paste is uploaded — all processing runs locally. Read more →

Why Tailwind v4 rewrote every color in OKLCH (and what it means for your hex codes)

9 min read #css #oklch #tailwind #color #design-system

In January 2025, Tailwind v4.0 shipped with one quietly radical change: every color in the default palette is defined in OKLCH instead of hex or RGB.

/* Tailwind v3 — hex */
.bg-blue-500 { background-color: #3b82f6; }

/* Tailwind v4 — OKLCH */
.bg-blue-500 { background-color: oklch(0.623 0.214 259.815); }

The visible difference is subtle: gradients between Tailwind shades look smoother, the color scale feels more evenly spaced when you flip between blue-100 and blue-900, and on display-P3 monitors the saturated colors are noticeably more vivid.

The under-the-hood difference is two decades of accumulated CSS hacks getting replaced with a color space designed for the job.

This post is what changed, why, and how to think about it for your own design system.

The problem with hex (and HSL)

Hex colors are RGB — three channel values from 0–255. RGB is how display hardware addresses colors, and it’s accurate. But it’s not perceptually uniform: equal numerical changes in RGB don’t correspond to equal visible changes.

The same effect plagued HSL. HSL parameterizes colors by hue, saturation, and lightness — which sounds intuitive — but its “lightness” axis is calculated from RGB without accounting for how human eyes perceive brightness. The blue at HSL (220, 100%, 50%) looks darker than the yellow at (60, 100%, 50%), even though both have L=50%. Yellows are perceptually brighter than blues at the same numeric lightness.

For a design system trying to generate “blue-100 through blue-900” where each step looks evenly spaced, this is a problem. Tailwind v3’s blue-500 to blue-600 step looks like a smaller change than blue-700 to blue-800. The numbers say the steps are equal; your eyes say otherwise.

What OKLCH fixes

OKLCH was designed by Björn Ottosson in 2020 specifically to solve this. It’s a polar-coordinate version of OKLab, which is itself a perceptually-uniform reformulation of CIE Lab. Three components:

Same example, in OKLCH:

.bg-blue-500 { background-color: oklch(0.623 0.214 259.815); }
.bg-blue-600 { background-color: oklch(0.546 0.215 262.881); }
.bg-blue-700 { background-color: oklch(0.488 0.217 264.376); }

Notice the L values change by ~5% per step. That’s the design. In hex, the equivalent jumps are unevenly sized in perceptual terms; in OKLCH, they’re calibrated.

What Tailwind v4 actually changed

Three concrete changes in v4:

1. The default palette is now OKLCH

Every color in tailwindcss/colors ships as an oklch() value. Your bg-blue-500, your text-red-700, your border-gray-300 — all the same hue family they were, but with the lightness curve recalibrated.

2. The palette uses display-P3 where possible

OKLCH can specify colors outside the sRGB gamut. Tailwind v4 takes some advantage of this — saturated colors in the v4 palette are slightly more vivid on P3 displays than they were in v3. On sRGB displays they fall back via clamping.

You won’t see a dramatic difference unless you’re on a recent Mac or a high-end monitor, but the brand colors look more saturated where the hardware supports it.

3. Custom-color generation works perceptually

If you define --brand-500: oklch(0.6 0.15 250); and then derive --brand-100 and --brand-900 by adjusting L by ±35%, you get shades that look evenly distributed. With hex/RGB or HSL, you have to hand-tune.

This is why most modern design tools (Radix Colors, OKHSL palette generators, Anthropic’s library tweaks for accessibility) all use OKLCH or OKLab under the hood now.

The “but my hex codes” question

If you’ve been using Tailwind v3 with custom colors defined in hex, upgrading to v4 doesn’t break anything. Tailwind v4 still accepts hex; the palette just happens to be defined in OKLCH internally. Your #1a73e8 keeps working.

The win comes when you migrate your own custom palette. Two paths:

Path 1: keep hex, get OKLCH-equivalent shades

If you have a brand color you can’t change (#2563eb is your brand blue), you can derive an OKLCH equivalent and then generate the rest of the scale from it:

/* The brand color */
--brand-500: #2563eb;

/* Equivalent OKLCH */
--brand-500-oklch: oklch(0.546 0.215 262.881);

/* Generate scale by adjusting L */
--brand-100: oklch(0.94 0.04 262);   /* light tint */
--brand-300: oklch(0.78 0.13 262);
--brand-700: oklch(0.42 0.21 262);
--brand-900: oklch(0.28 0.16 262);   /* dark shade */

You can use our converter to find the OKLCH for any hex (paste the hex, read off the OKLCH field). Then adjust L and re-derive the scale.

Path 2: redefine your palette in OKLCH from the start

For new projects: pick your brand color in OKLCH directly. Tools like oklch.fyi and oklch.com let you visually pick. The advantage: from day one, your scales look perceptually uniform without manual tweaking.

The color-mix() superpower

OKLCH pairs naturally with CSS’s color-mix() function:

.btn-primary { background: var(--brand); }
.btn-primary:hover {
  /* Mix 80% of brand color with 20% black, in OKLCH space */
  background: color-mix(in oklch, var(--brand) 80%, black);
}

In OKLCH space, “20% darker” is well-defined: lower the L by 20%. In RGB, “20% darker” is ambiguous — RGB scaling clips bright channels and produces inconsistent results.

This replaces the typical SCSS pattern of pre-computing --brand-hover as a separate variable. Define one base color, derive variants on the fly.

Browser support (April 2026)

FeatureChromeSafariFirefox
oklch() color literal111+ (March 2023)15.4+ (March 2022)113+ (May 2023)
color-mix(in oklch, ...)111+16.2+113+
oklab() and lab()111+15+113+
display-p3 color literal111+15+113+
Relative color syntax (from)119+18+128+

OKLCH itself is Baseline as of mid-2023 — safe to use without a fallback in any modern target. For IE11 / very old Safari, define a hex fallback:

.brand {
  color: #2563eb;                          /* fallback */
  color: oklch(0.546 0.215 262.881);       /* modern */
}

Common questions

”Will users see different colors?”

For Tailwind users on common monitors: a tiny bit, mostly imperceptible. Tailwind chose OKLCH values that closely match the v3 hex equivalents at the central shades. Edge shades (50, 950) shifted slightly because the v3 palette wasn’t perceptually uniform there.

On P3-capable displays (most Apple devices since 2017, recent iPhones and iPads, MacBook Air M2+), saturated colors look slightly more vivid in v4 than v3 because OKLCH can express colors outside sRGB.

”Should I migrate immediately?”

If you’re on Tailwind v3, the v4 upgrade is non-breaking for your hex colors. The migration is mostly: decide whether you want the new palette’s recalibrated shades (default) or pin your custom colors to v3 equivalents (configurable). Read Tailwind v4 release notes for details.

If you’re not on Tailwind: you don’t need to migrate to use OKLCH. Replace your oklch() values for new hover/active variants while keeping existing hex base colors. Adopt incrementally.

”Is OKLCH the future or is something else coming?”

OKLCH is part of CSS Color Module Level 4, which is standardized and shipping. The successor (CSS Color Module Level 5) adds more features (nested color functions, light-dark(), relative colors) but builds on top of OKLCH rather than replacing it.

For practical purposes, OKLCH is stable. Investing in an OKLCH-based design system in 2026 isn’t a bet on a future spec; it’s adopting the current one.

Try the conversion

Use our color converter to translate between hex, RGB, HSL, and OKLCH. The “OKLCH” field shows the canonical form for any color you paste in. Combined with our WCAG contrast checker, you can verify a palette is accessible across the full L axis without leaving the browser.

For exploring OKLCH visually, oklch.com is the best free interactive tool — adjust L, C, H sliders and see the gamut limits live.

Further reading