OKLCH in CSS:
Why we moved from RGB and HSL
# The extremely short version
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.
Max sRGB Green (approx)
Vivid & Neon (on P3 displays)
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.
L=50%
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 Bug */
.cold {
/* Looks Blue */
bg: lch(35% 110 300);
}
.very-cold {
/* Turns Purple! */
bg: lch(35% 75 300);
}/* 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.
0 (Black) to 1 (White)
0 (Gray) to ~0.37 (Vivid)
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.
: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.
2. Use Stylelint
Prevent old formats from creeping back in.
{
"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:
.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.