OKLCH Color Space — Perceptually Uniform Colors for CSS
OKLCH is a perceptually uniform color space that makes predictable color palettes, consistent lightness, and accessible contrast easy. Learn the oklch() CSS function, how to...
OKLCH is the best color space for UI design in 2024. Unlike HSL, colors at the same L value actually look equally bright — meaning you can mix colors across a palette without some looking washed out and others looking vibrant.
Pick colors with the Color Picker.
OKLCH syntax
/* oklch(lightness chroma hue) */
oklch(L C H)
/* L: 0 = black, 1 = white (or 0% to 100%) */
/* C: chroma (colorfulness), 0 = gray, ~0.37 = most vivid */
/* H: hue angle 0-360 */
/* Examples: */
oklch(0.5 0.2 250) /* Medium blue */
oklch(70% 0.15 30) /* Warm orange */
oklch(0.9 0.05 150) /* Light green */
/* With alpha: */
oklch(0.5 0.2 250 / 0.5) /* 50% transparent blue */
Why OKLCH beats HSL
/* HSL "same lightness" problem: */
hsl(0, 100%, 50%) /* Red — looks very bright */
hsl(240, 100%, 50%) /* Blue — looks darker than red at "same" 50% L */
hsl(120, 100%, 50%) /* Green — looks even brighter */
/* HSL lightness is not perceptually uniform.
Different hues appear to have different brightness at the same value. */
/* OKLCH: same L = actually same perceived brightness */
oklch(0.5 0.2 30) /* Orange — same perceived brightness as... */
oklch(0.5 0.2 250) /* Blue — they actually look equally bright */
oklch(0.5 0.2 140) /* Green — same brightness too */
Building a tonal palette
OKLCH makes it easy to create systematic palettes — just vary lightness:
:root {
/* Blue palette: same C and H, vary L */
--blue-50: oklch(0.97 0.04 250); /* Very light */
--blue-100: oklch(0.93 0.07 250);
--blue-200: oklch(0.87 0.10 250);
--blue-300: oklch(0.78 0.14 250);
--blue-400: oklch(0.67 0.18 250);
--blue-500: oklch(0.56 0.22 250); /* Base */
--blue-600: oklch(0.47 0.22 250);
--blue-700: oklch(0.38 0.18 250);
--blue-800: oklch(0.30 0.14 250);
--blue-900: oklch(0.22 0.09 250);
--blue-950: oklch(0.15 0.05 250); /* Very dark */
/* Same approach for any hue: just change H */
--red-500: oklch(0.56 0.22 30);
--green-500: oklch(0.56 0.22 145);
--purple-500: oklch(0.56 0.22 300);
/* Each at the same L=0.56 looks equally bright! */
}
WCAG contrast with OKLCH
/* APCA contrast: use L for approximate checking */
/* Target: 4.5:1 ratio for normal text (WCAG AA) */
/* Good contrast pairs (L difference ≈ 0.5+): */
background: oklch(0.97 0.04 250); /* L=0.97 */
color: oklch(0.20 0.15 250); /* L=0.20 → large L difference */
/* Accessible neutral text: */
:root {
--text-strong: oklch(0.15 0.01 265); /* Near black */
--text-default: oklch(0.30 0.02 265); /* Dark gray */
--text-muted: oklch(0.50 0.02 265); /* Gray — check contrast! */
--bg-base: oklch(1.00 0 0); /* White */
--bg-subtle: oklch(0.97 0.01 265); /* Off white */
--bg-muted: oklch(0.93 0.02 265); /* Light gray */
}
Chroma limits by hue
Chroma (C) maximum varies by hue because some hue+chroma combinations fall outside the sRGB gamut:
/* Max chroma varies by hue (rough guide): */
/* Red (H≈30): max ~0.28 */
/* Yellow (H≈90): max ~0.18 */
/* Green (H≈145): max ~0.28 */
/* Cyan (H≈200): max ~0.32 */
/* Blue (H≈265): max ~0.32 */
/* Purple (H≈310):max ~0.26 */
/* Magenta(H≈340):max ~0.28 */
/* Safe across all hues: */
/* C ≤ 0.15 — stays in sRGB */
/* High-chroma (P3 gamut): */
/* C ≤ 0.28 — needs color-gamut: p3 check */
@media (color-gamut: p3) {
:root {
--accent: oklch(0.6 0.28 250); /* Vivid P3 blue */
}
}
/* Fallback (no @media needed — browsers clip to sRGB automatically): */
:root {
--accent: oklch(0.6 0.22 250); /* sRGB-safe blue */
}
CSS color-mix() with OKLCH
/* Interpolate in OKLCH for perceptually correct gradients: */
.button {
background: color-mix(in oklch, var(--blue-500), var(--green-500) 50%);
}
/* Transparent to color (no "gray middle" problem): */
.gradient {
background: linear-gradient(
in oklch,
oklch(0.6 0.3 30),
oklch(0.6 0.3 250)
);
/* Hue travels through yellow/green naturally, not through gray */
}
JavaScript: generate OKLCH palette
function generatePalette(hue, chroma = 0.22, steps = 11) {
const lightnesses = [0.97, 0.93, 0.87, 0.78, 0.67, 0.56, 0.47, 0.38, 0.30, 0.22, 0.15];
return lightnesses.slice(0, steps).map((l, i) => ({
step: i * 100 + 50,
css: `oklch(${l} ${chroma * (l < 0.2 || l > 0.9 ? 0.5 : 1)} ${hue})`,
}));
}
generatePalette(250);
// [
// { step: 50, css: 'oklch(0.97 0.11 250)' },
// { step: 150, css: 'oklch(0.93 0.22 250)' },
// ...
// ]
Browser support
OKLCH works in Chrome 111+, Firefox 113+, Safari 15.4+. For older browsers, provide HSL fallback:
.element {
color: hsl(240, 60%, 40%); /* Fallback */
color: oklch(0.40 0.18 265); /* Modern browsers */
}
Related tools
- Color Picker — pick and convert colors
- CSS Grid Generator — layout with design tokens
- Box Shadow Generator — pick shadow colors
Related posts
- WCAG Contrast Explained (AA vs AAA, When It Matters) — Color contrast determines who can read your interface. This is the WCAG math, th…
- Hex, RGB, HSL, OKLCH: Which to Pick in 2026 — Four CSS color formats, four different audiences. This is what each is good at, …
- Color Accessibility — WCAG Contrast Ratios and Tools — WCAG 2.1 requires a 4.5:1 contrast ratio for normal text and 3:1 for large text.…
- HSL Color — Hue, Saturation, and Lightness Explained — HSL represents colors as hue (0–360°), saturation (0–100%), and lightness (0–100…
Related tool
Pick colors, convert hex/RGB/HSL/OKLCH, and check WCAG contrast.
Written by Mian Ali Khalid. Part of the Frontend & Design pillar.