Using OKLCH in CSS: A Practical Guide to Modern Color Design

oklch() is a new way to define CSS colors. In oklch(L C H / a):

  • L Perceived Lightness (0-1). Consistent for our eyes, unlike HSL.
  • C Chroma. From gray (0) to saturated (approx 0.37).
  • H Hue (0-360). The color wheel angle.
a:hover {
  color: oklch(0.45 0.26 264);       /* Blue */
  color: oklch(1 0 0);             /* White */
  color: oklch(0 0 0 / 50%);     /* Black, 50% opacity */
}

How CSS colors have changed

The CSS Color Module Level 4 spec brought us new syntactic sugar (removing commas in rgb) but more importantly, it introduced P3 Wide Gamut Colors.

Standard sRGB only covers 35% of human-visible colors. Modern Apple devices and OLED screens support Display P3, which adds 30% more vivid colors. Old formats like Hex and RGB cannot describe these neon-like colors.

sRGB Green

Max sRGB Green (approx)

P3 Green

Vivid & Neon (on P3 displays)

[Image of CIE 1931 chromaticity diagram comparing sRGB and P3 gamuts]

To use P3, we need a format that supports it. rgb() and hsl() are mathematically limited to sRGB. We could use color(display-p3 ...), but it's hard to read. OKLCH is the perfect middle ground: readable and supports wide gamut.

Why OKLCH beats HSL & LCH

The HSL Lightness Problem

HSL is a cylindrical geometry. It forces "50% Lightness" to mean different things for different hues. Blue at 50% L is much darker than Yellow at 50% L.

HSL Blue
L=50%
HSL Yellow
L=50%

Notice how Yellow feels much brighter despite identical Lightness values.

The LCH "Blue Hue Shift"

Standard LCH fixes the lightness issue but introduces a bug: changing chroma in blue colors shifts the hue towards purple. OKLCH fixes this math.

LCH
/* LCH Bug */
.cold {
  /* Looks Blue */
  bg: lch(35% 110 300); 
}
.very-cold {
  /* Turns Purple! */
  bg: lch(35% 75 300);
}
OKLCH
/* OKLCH Fix */
.cold {
  /* Looks Blue */
  bg: oklch(0.48 0.27 274);
}
.very-cold {
  /* Stays Blue */
  bg: oklch(0.48 0.18 274);
}

How OKLCH works

Colors are encoded with 4 numbers.

L
Lightness

0 (Black) to 1 (White)

C
Chroma

0 (Gray) to ~0.37 (Vivid)

H
Hue

0 - 360 (Angle)

Native Color Modification

With CSS Color 5, we can modify colors natively. OKLCH is the best format for this because of its predictable lightness.

CSS
:root {
  --origin: #ff0000;
}

.button:hover {
  /* Keep hue/chroma, reduce lightness by 0.1 */
  background: oklch(from var(--origin) calc(l - 0.1) c h);
}

Migration Guide

1. Convert Existing Colors

You can replace hex/rgb/hsl with OKLCH safely. Browsers support it natively now.

npx convert-to-oklch ./src/**/*.css

2. Use Stylelint

Prevent old formats from creeping back in.

JSON
{
  "plugins": ["stylelint-gamut"],
  "rules": {
    "color-function-notation": "modern",
    "color-no-hex": true,
    "function-disallowed-list": ["rgba", "hsla", "rgb", "hsl"]
  }
}

3. Fallbacks (Optional)

If you still support very old browsers:

CSS
.bg {
  background-color: #4a90e2; /* Fallback */
  background-color: oklch(65% 0.15 240);
}

Summary: Why we switched

  • Readability We can understand lightness and chroma just by looking at the numbers.
  • Predictable Modifications Dynamic hover states and themes work without "muddy" colors or contrast issues.
  • P3 Support Access to the full range of modern display colors.