Skip to content

OKLCH Color Space: The Modern Alternative to HSL and RGB

OKLCH is a perceptually uniform color space that shipped in CSS Color Module Level 4 and is now the default format for Tailwind v4, Radix Colors, and most serious design systems built in 2026. The reason developers are switching is simple. Equal numeric steps in OKLCH look like equal visual steps to the human eye. RGB and HSL do not have this property, and that gap is responsible for nearly every awkward gradient, broken hover state, and uneven dark mode palette you have ever had to hand-tune.

Why RGB and HSL Fall Short

RGB describes color as the intensity of red, green, and blue light. It matches how a monitor renders pixels, but it has nothing to do with perception. The value rgb(255, 200, 0) and rgb(0, 200, 255) both hold green at 200, but the first reads as bright yellow and the second as medium cyan. There is no way to lighten or darken an RGB color without shifting hue.

HSL was supposed to fix that. It splits color into hue, saturation, and lightness, so you can hold the hue constant and only change the lightness slider. In practice, HSL lightness is mathematically defined but perceptually broken. hsl(60, 100%, 50%) (yellow) looks far brighter than hsl(240, 100%, 50%) (blue), even though both claim 50% lightness. This is why HSL palettes always need manual tweaking. The same lightness number produces very different perceived brightness across hues.

OKLCH fixes this with a perceptually uniform L axis. L=0.6 reads as the same perceived brightness whether the hue is yellow, blue, or red. Equal L steps produce visually equal lightness steps. That single property eliminates more design-system bugs than any other CSS feature shipped in the last decade.

OKLCH Anatomy

The format is oklch(L C H) or oklch(L C H / alpha).

  • L (Lightness): 0 to 1, or 0% to 100%. 0 is pure black, 1 is pure white. 0.5 sits at the middle perceived brightness.
  • C (Chroma): 0 to about 0.4. Zero is fully gray, higher values mean more saturation. Maximum reachable chroma changes with hue and lightness because the sRGB gamut is not a perfect sphere.
  • H (Hue): 0 to 360 degrees, same convention as HSL. 0 is red, 90 is yellow, 180 is green, 270 is blue.

Example: oklch(0.7 0.15 250) is a mid-bright blue with moderate saturation.

Same Color in Three Formats

ColorRGBHSLOKLCH
Indigo 500rgb(99, 102, 241)hsl(239, 84%, 67%)oklch(0.59 0.20 277)
Emerald 500rgb(16, 185, 129)hsl(160, 84%, 39%)oklch(0.70 0.15 162)
Amber 500rgb(245, 158, 11)hsl(38, 92%, 50%)oklch(0.77 0.16 70)

Notice how the HSL lightness values (67%, 39%, 50%) are all over the place even though these three colors read as roughly similar mid-brightness palette accents. In OKLCH, the L values (0.59, 0.70, 0.77) honestly reflect that yellow looks lighter than green which looks lighter than blue at the same chromatic intensity. That predictability is the entire selling point.

Building a Palette in OKLCH

The standard pattern for a tinted scale (50, 100, 200, ..., 900, 950) is to hold hue roughly constant, fade chroma down at the ends, and step lightness uniformly. Here is a minimal blue scale.

:root {
  --blue-50:  oklch(0.97 0.02 250);
  --blue-100: oklch(0.93 0.04 250);
  --blue-200: oklch(0.87 0.08 250);
  --blue-300: oklch(0.78 0.13 250);
  --blue-400: oklch(0.68 0.18 250);
  --blue-500: oklch(0.58 0.20 250);
  --blue-600: oklch(0.48 0.20 250);
  --blue-700: oklch(0.40 0.17 250);
  --blue-800: oklch(0.32 0.13 250);
  --blue-900: oklch(0.24 0.09 250);
  --blue-950: oklch(0.17 0.06 250);
}

Try this same exercise in HSL and you will spend hours fiddling with the saturation and lightness on every individual step. In OKLCH the math is the design.

Color Mixing and Interpolation

CSS gradients and the new color-mix() function can interpolate inside any color space. Doing it in OKLCH avoids the muddy gray midpoints that plague RGB interpolation between complementary colors.

/* RGB interpolation: muddy gray in the middle */
background: linear-gradient(to right, #ff0000, #0000ff);

/* OKLCH interpolation: vivid purple midpoint */
background: linear-gradient(in oklch, #ff0000, #0000ff);

/* Mixing two colors halfway in OKLCH */
color: color-mix(in oklch, red, blue);

Browser Support and Fallbacks

OKLCH has been supported in Chrome, Safari, Firefox, and Edge since 2023. As of 2026 it covers more than 95% of global traffic with no polyfill needed. Old browsers ignore unknown color values, so a simple cascade fallback is enough.

.btn {
  background: #6366f1;                  /* fallback */
  background: oklch(0.59 0.20 277);     /* modern */
}

The one real gotcha is gamut clipping. The sRGB gamut cannot represent every OKLCH value you can write. A high-chroma blue at L=0.8 may not exist in sRGB and the browser will clip it to the nearest reachable color. Use the @media (color-gamut: p3) query or the display-p3 variant to opt into a wider gamut on modern displays.

When You Still Reach for HSL or HEX

  • HEX stays the right format for assets that need to be parsed by tools outside the browser (mobile native, design tokens consumed by Figma, email templates).
  • HSL is still fine for quick prototyping if perceptual uniformity does not matter. It is also slightly more readable to designers who learned color picker UIs in the HSL era.
  • RGB remains the right answer when you are pulling pixel data from a canvas or image, since that is the format the bytes are already in.

Common Mistakes

  • Writing L as a percentage of 100 instead of 0 to 1. Both work, but mixing them in the same file leads to surprising results. Pick one convention and stick with it.
  • Treating chroma as if it had a fixed max of 0.4. Maximum chroma varies by hue. Yellow tops out lower than blue. A scale that hits 0.3 chroma on blue may need to drop to 0.2 on yellow to stay inside sRGB.
  • Forgetting hue interpolation modes. When mixing across the color wheel, specify shorter hue, longer hue, or increasing hue to control which arc the gradient takes around the wheel.
  • Shipping OKLCH without a fallback for ancient browsers. Modern coverage is excellent, but if your target audience includes locked-down corporate IE11 holdouts, ship a HEX fallback too.

TL;DR

  • OKLCH is perceptually uniform. Equal L steps look equal to the eye.
  • It is the default in Tailwind v4 and the format design systems are standardizing on.
  • Format is oklch(L C H) with L in 0 to 1, C in 0 to 0.4, H in 0 to 360.
  • Use in oklch in gradients and color-mix() for clean interpolation.
  • Browser support is 95%+. Provide a HEX fallback only if you support genuinely old browsers.
  • Watch for sRGB gamut clipping on high-chroma values.

Convert between OKLCH, HSL, RGB, and HEX

Use our free Color Converter to translate any color between modern and legacy formats. Paste, tweak, and copy in one click. 100% client-side, no data leaves your browser.

Open Color Converter