Hex to OKLCH: Why Tailwind v4 Switched and How to Convert Any Palette
Quick answer: OKLCH is a perceptually uniform color space written as oklch(L C H) — lightness 0–100%, chroma starting at 0, hue 0–360°. Tailwind v4 defines every default color in it because equal steps in lightness look equal, and because OKLCH can address colors outside sRGB on P3 displays. Converting a hex palette is a lossless coordinate change: same color, better axes to work on.
The three numbers
- L — lightness.
0%is black,100%is white. Unlike HSL's lightness, this one tracks what your eye reports. - C — chroma. Colorfulness, starting at
0for gray. It has no fixed maximum; the usable ceiling depends on the lightness and hue you are at. - H — hue. Degrees, 0–360, same mental model as HSL. About 30° is red, 145° green, 265° blue.
/* the same color, three ways */
#7c5cff
rgb(124 92 255)
oklch(58.6% 0.223 285.4)
Why HSL lies about lightness
Try this in any browser:
hsl(60 100% 50%) /* yellow */
hsl(240 100% 50%) /* blue */
Both claim 50% lightness. The yellow is blinding; the blue is nearly black. HSL's lightness is a midpoint of RGB channel values — a math artifact, not a perceptual measurement. That is why HSL ramps built by stepping L evenly produce scales where some steps jump and others barely move.
OKLCH is derived from OKLab, a perceptual color space designed so that numeric distance corresponds to perceived difference. Two colors at oklch(60% …) read as equally light regardless of hue. This single property is why the whole design-token world moved.
| Space | Even lightness steps? | Hue stays put when lightening? | Reaches P3? | Good for |
|---|---|---|---|---|
| Hex / RGB | No | No | No | Storing a final value |
| HSL | No | Mostly, but shifts | No | Quick eyeball tweaks |
| LCH (CIE) | Close | Blue shifts to purple | Yes | Print-adjacent work |
| OKLCH | Yes | Yes | Yes | Design tokens and ramps |
Why Tailwind v4 made the switch
Tailwind CSS v4 ships its entire default palette as oklch() values. Two payoffs:
- Wider gamut. sRGB hex cannot express the most saturated colors a modern P3 display can show. OKLCH coordinates can, so v4's
red-500is genuinely more vivid on a recent laptop or phone than the v3 hex equivalent — and gamut-maps back gracefully on older screens. - Ramps that behave. A 50–950 scale generated by stepping OKLCH lightness produces uniform-feeling steps. The same scale generated in HSL bunches up in the middle and washes out at the ends.
The practical consequence for anyone porting a site to Tailwind v4: your theme block is CSS-first, and the values slot in as custom properties.
@import "tailwindcss";
@theme {
--color-brand-500: oklch(58.6% 0.223 285.4);
--color-brand-600: oklch(51.2% 0.216 285.4);
--font-display: "Space Grotesk", system-ui, sans-serif;
}
Converting hex to OKLCH
The math is a fixed pipeline, not a lookup table:
- Parse the hex to sRGB channel values, 0–255.
- Undo gamma to get linear sRGB.
- Apply the OKLab matrix transform (Björn Ottosson's
M1, a cube-root step, thenM2). - Convert the resulting
a/baxes to polar form: chroma is the magnitude, hue is the angle.
It is exactly reversible, so nothing is lost going hex → OKLCH → hex. In practice nobody does this by hand. Reading it off the live page is faster than pasting hex codes into a converter one at a time:
- Open any site with CSS DNA and click Extract.
- In the palette panel, switch the format toggle to OKLCH — every extracted color re-renders as
oklch(), ready to copy. - Click any swatch to generate its full 50–950 tonal scale, built in OKLCH around that color's hue.
Building a ramp that doesn't go neon at the ends
The naive approach — hold hue and chroma, step lightness — fails at the extremes. At L = 0.97 full chroma gives you a fluorescent pastel; at L = 0.22 it gives you a muddy near-black. The fix is to taper chroma toward both ends, keeping it highest around the mid-tones where the eye can actually resolve saturation.
The rule, in words: fix the hue, step lightness from about 0.97 down to 0.22 across eleven stops, and scale chroma by how close each stop sits to the mid-lightness anchor.
A single hue (285°) at eleven lightness stops, with chroma tapered toward both ends. Every step is the same perceptual distance from its neighbor.
This is the same construction Tailwind's own palette uses, and it is what CSS DNA generates when you click a swatch — so a color pulled off any site comes back as a complete, usable scale rather than one isolated value.
The gamut caveat
Because chroma is unbounded, it is easy to write an oklch() value no sRGB display can show. Browsers handle this by gamut-mapping to the nearest displayable color rather than erroring, so nothing breaks — but two colors you wrote as distinct can collapse to the same rendered value on an sRGB screen.
If a distinction matters, check it on a non-P3 display, or pin the critical steps to values you have verified. And remember that contrast requirements are evaluated on the rendered color: a P3-vivid button that gamut-maps darker on an older monitor may pass or fail differently. Worth a pass through a WCAG and APCA check either way.
Evidence and sources
- Claim
oklch()is supported across all current major browser engines, and Tailwind CSS v4 defines its default color palette in it.- Basis
- CSS Color Module Level 4 defines
oklch(). Engine support: Safari 15.4, Chrome 111, Firefox 113 — Baseline widely available since 2023. Tailwind v4 release notes document the palette move to OKLCH and the CSS-first@themeblock. - Source
- W3C CSS Color 4 — OKLab and OKLCH · MDN oklch() · Tailwind CSS colors documentation · Björn Ottosson — A perceptual color space for image processing
- Verified
- August 11, 2026.
- Limitations
- Perceptual uniformity in OKLab is a very good approximation, not a guarantee for every hue. Extreme chroma near the gamut boundary still shows small inconsistencies.
When hex is still the right answer
OKLCH is the space to work in. Hex remains fine for storage and hand-off — email templates, older build tooling, anything that has to survive a system you do not control. The two are the same color, so keep both: OKLCH in your theme block where you generate and adjust, hex in the places that only need a final value.
Frequently asked questions
What is OKLCH in CSS?
OKLCH is a perceptually uniform CSS color space written as oklch(L C H): lightness from 0% to 100%, chroma as an unbounded saturation value starting at 0, and hue in degrees from 0 to 360. Equal steps in L look equally different to the eye.
Why does Tailwind CSS v4 use OKLCH?
Tailwind v4 defines its default palette in oklch() for two reasons: OKLCH can express colors outside the sRGB gamut, so shades look more vivid on P3 displays, and its lightness axis is perceptually even, so the 50 to 950 ramp steps look uniform.
How do I convert a hex color to OKLCH?
Convert hex to linear sRGB, apply the OKLab matrix transform, then express the a and b axes as polar chroma and hue. In practice use a converter — CSS DNA shows every extracted color in OKLCH with a one-click format toggle.
What is the difference between OKLCH and HSL?
HSL lightness is a math artifact, not a perception: hsl(60 100% 50%) yellow and hsl(240 100% 50%) blue claim identical lightness but look nothing alike. OKLCH lightness tracks perceived brightness, so two colors at the same L read as equally light.
Is OKLCH supported in all browsers?
Yes in every current major engine. Safari shipped oklch() in 15.4, Chrome in 111 and Firefox in 113, making it Baseline widely available since 2023. Browsers gamut-map out-of-range values automatically rather than failing.
How do I build a 50 to 950 color ramp in OKLCH?
Hold the hue constant, step lightness from about 0.97 down to 0.22 across the eleven stops, and taper chroma toward both ends. Full chroma at the extremes produces neon-looking 50s and muddy 950s.
See any site's colors in OKLCH
One toggle for hex, RGB, HSL or OKLCH — plus a full 50–950 ramp from any swatch. Free to inspect.
Add CSS DNA to Chrome — Free