Why Tailwind v4 rewrote every color in OKLCH (and what it means for your hex codes)
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:
- L (lightness) — 0% to 100%, perceptually uniform. A 10% jump on the L axis looks the same regardless of hue.
- C (chroma) — 0 to ~0.4, how vivid the color is. 0 is gray.
- H (hue) — 0–360°, polar position around the color wheel.
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)
| Feature | Chrome | Safari | Firefox |
|---|---|---|---|
oklch() color literal | 111+ (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 literal | 111+ | 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
- Tailwind v4.0 release notes
- OKLCH in CSS — Evil Martians — the seminal explainer
- Falling for OKLCH — Smashing Magazine
- CSS Color Module Level 4 spec
- Our reference: What is a hex color?, Hex vs RGB vs HSL vs OKLCH
- Related: hex.tooljo.com — convert between hex / RGB / HSL / OKLCH and check WCAG contrast in one widget.