OKLCH vs HSL: Why Modern CSS Color Uses OKLCH

HSL says yellow and blue have the same lightness — your eyes disagree. What perceptual uniformity means, and where OKLCH changes gradients and palettes.

HSL was a gift when it arrived in CSS: finally, dials that made sense — hue, saturation, lightness — instead of inscrutable hex codes. But HSL has a flaw baked into its math, and once you see it you can’t unsee it: HSL’s “lightness” lies.

The lie, in one comparison

Ask HSL for yellow and blue at identical 50% lightness: hsl(60 100% 50%) and hsl(240 100% 50%).

hsl(60 100% 50%)“50% lightness”
#FFFF00background
hsl(240 100% 50%)“50% lightness”
#0000FFbackground

Same number, wildly different brightness: the yellow’s relative luminance is about 0.93, the blue’s about 0.07.

HSL is a geometric remapping of the RGB cube — its lightness is an average of channel values, not a model of human vision. Our eyes are far more sensitive to green-yellow light than to blue, and HSL simply ignores that. The practical damage: any system built on “same L means same prominence” — theme palettes, chart scales, auto-generated shades — quietly produces colors with completely different visual weight and completely different contrast ratios.

What OKLCH fixes

oklch(L C H) is built on OKLab, a color space designed so that numeric distance matches perceived difference. Same three friendly dials, but now they tell the truth:

  • Equal L looks equally bright across hues — an L=0.7 red, green, and blue all carry similar visual weight, and similar contrast against a given background.
  • Hue stays put. Darkening a blue in HSL drifts it toward purple; in OKLCH, H is perceptual, so shades of a brand color stay on-brand. This is what makes the keep-the-hue contrast fix work.
  • Access to more colors. OKLCH can address the wider P3 gamut modern screens ship with; HSL is fenced into sRGB.

Where you see it most: gradients

Blend two saturated, hue-distant colors in plain sRGB and the middle of the gradient sags into gray — the notorious dead zone. Interpolating in OKLCH keeps the midpoints vivid:

sRGB interpolation
#1392E6start stop#E40078end stop
OKLCH interpolation
#1392E6start stop#E40078end stop

Blue to magenta, same stops. The sRGB blend dulls in the middle; the OKLCH blend stays saturated. (Rendered live by your browser with linear-gradient(in oklch, …).)

Support and syntax

oklch() has shipped in every major browser since 2023 (Chrome 111, Safari 16.4, Firefox 113) — as has gradient interpolation syntax. Usage looks like:

.button {
  background: oklch(0.55 0.17 250);        /* L C H */
  background: oklch(0.55 0.17 250 / 80%);  /* with alpha */
}
.hero {
  background: linear-gradient(in oklch, #1392e6, #e40078);
}

HSL isn’t going anywhere and stays fine for quick one-off tweaks. But for anything systematic — palettes, shade scales, gradient ramps, contrast math — reach for OKLCH first.

Try it in the tool

Everything on PaletteSense runs on OKLCH: the palette generator rotates hues at constant perceptual lightness, the gradient editor blends in OKLCH with live contrast analysis, and every page in the color library lists the OKLCH values.