OKLCH Color Space Explained
The Problem OKLCH Solves
Traditional color spaces like HSL and RGB have a critical flaw: they don't match human perception. In HSL, a lightness value of 50% doesn't look equally bright across different hues. A yellow at HSL lightness 50% appears much brighter to the eye than a blue at the same value. This makes HSL unreliable for design systems.
RGB is worse — it's tied to hardware (red, green, blue phosphors) and tells you nothing about how a color actually looks to a human.
OKLCH (Okabe-Lab Chroma-Hue) fixes this by being perceptually uniform. Equal numeric changes produce equal visual changes, regardless of hue. This makes it the first color space that works the way designers actually think.
How OKLCH Works
OKLCH uses three components:
| Component | What It Controls | Range | Analogy |
|---|---|---|---|
| L (Lightness) | Perceived brightness | 0–1 | HSL lightness, but accurate |
| C (Chroma) | Color intensity/saturation | 0–0.4+ | HSL saturation, but uniform |
| H (Hue) | Color angle on the wheel | 0–360 | Same as HSL hue |
oklch(L C H)
oklch(0.7 0.15 150) /* A medium-bright green */
Why Perceptual Uniformity Matters
Consider two colors at the same HSL lightness (50%):
| Color | HSL | Perceived Brightness |
|---|---|---|
| Yellow | hsl(50, 100%, 50%) | Very bright |
| Blue | hsl(230, 100%, 50%) | Much darker |
In OKLCH, you'd express these with different lightness values to match their actual perceived brightness:
| Color | OKLCH | Perceived Brightness |
|---|---|---|
| Yellow | oklch(0.82 0.18 90) | 0.82 (bright) |
| Blue | oklch(0.45 0.15 260) | 0.45 (dark) |
This is the key difference: OKLCH lightness matches what your eyes see.
OKLCH vs Other Color Spaces
| Feature | OKLCH | HSL | RGB | LCH |
|---|---|---|---|---|
| Perceptually uniform | Yes | No | No | Mostly |
| Hue-independent lightness | Yes | No | No | Mostly |
| CSS support | Modern browsers | All | All | Modern browsers |
| Intuitive for design | Yes | Yes (misleadingly) | No | Yes |
| No hue shift in gradients | Yes | No | N/A | Some |
OKLCH vs LCH
LCH (CIE LCH) was an earlier attempt at perceptual uniformity. It's better than HSL but still has a known issue: the blue-purple shift. Adjusting lightness in LCH can unintentionally shift the hue, particularly in the blue range. OKLCH corrects this with a newer underlying model (OKLAB), making it the most perceptually accurate option available.
Practical Uses in CSS
Consistent Shade Scales
The biggest win: generate shade scales by changing only lightness while keeping chroma and hue constant. The result is visually even steps — no surprises:
:root {
--brand-50: oklch(0.97 0.03 260);
--brand-100: oklch(0.93 0.06 260);
--brand-200: oklch(0.85 0.10 260);
--brand-300: oklch(0.75 0.14 260);
--brand-400: oklch(0.65 0.17 260);
--brand-500: oklch(0.55 0.20 260);
--brand-600: oklch(0.45 0.18 260);
--brand-700: oklch(0.37 0.15 260);
--brand-800: oklch(0.28 0.12 260);
--brand-900: oklch(0.22 0.10 260);
--brand-950: oklch(0.15 0.08 260);
}
Every step looks equally different from its neighbor. Try that in HSL and you'll get inconsistent visual gaps.
Better Gradients
HSL gradients often pass through muddy middle tones because hue interpolation isn't perceptually uniform. OKLCH gradients stay vivid:
.gradient-hsl {
background: linear-gradient(to right, hsl(0, 80%, 55%), hsl(240, 80%, 55%));
/* Passes through dull gray in the middle */
}
.gradient-oklch {
background: linear-gradient(to right, oklch(0.6 0.2 25), oklch(0.5 0.2 280));
/* Stays vivid and perceptually even throughout */
}
Accessible Color Manipulation
Need a lighter version with the same perceived hue? In HSL, you'd adjust lightness and get unexpected hue shifts. In OKLCH, bump the L value and the color stays consistent:
/* HSL: making lighter shifts the apparent hue */
.button { background: hsl(230, 80%, 45%); } /* blue-ish */
.button-hover { background: hsl(230, 80%, 55%); } /* slightly purple shift */
/* OKLCH: making lighter preserves the hue */
.button { background: oklch(0.45 0.18 260); }
.button-hover { background: oklch(0.55 0.18 260); } /* same hue, brighter */
Browser Support
| Browser | Version | CSS Support |
|---|---|---|
| Chrome | 111+ | oklch() in color values |
| Firefox | 113+ | Full support |
| Safari | 15.4+ | Full support |
| Edge | 111+ | Full support |
For older browsers, provide hex/hsl fallbacks:
:root {
--brand-500: #3366ff; /* Fallback */
--brand-500: oklch(0.55 0.2 260); /* Modern override */
}
Browsers that don't understand oklch() will use the first declaration; those that do will use the second.
Converting Between Formats
| From | To OKLCH | Method |
|---|---|---|
| Hex | OKLCH | Use a color converter tool or library (culori, colorjs.io) |
| RGB | OKLCH | Same — the math requires intermediate LAB conversion |
| HSL | OKLCH | Direct conversion not possible; go HSL → RGB → OKLCH |
For build-time conversion, culori works well:
import { converter } from 'culori'
const toOklch = converter('oklch')
toOklch('#3366ff') // { mode: 'oklch', l: 0.55, c: 0.20, h: 260 }
Related Guides
Try It Yourself
Convert any color to OKLCH and see the perceptually uniform difference. Our color picker supports hex, RGB, HSL, and OKLCH with live preview.