What is oklch and Why Does it Matter?
Every color format you've used — hex, rgb, hsl — was designed to describe colors as computers render them, not as humans see them. This creates a persistent problem: two colors that appear to have the same lightness in hsl look dramatically different in brightness to the human eye. Blue at hsl(240, 100%, 50%) looks much darker than yellow at hsl(60, 100%, 50%) even though both have identical L: 50% values.
oklch solves this. It is a perceptually uniform color space — meaning that equal numerical changes in oklch values produce equal perceived changes in color appearance. If you set two colors to the same lightness value in oklch, they will genuinely look the same brightness to the human eye, regardless of their hue.
oklch stands for Ok (the "Ok" prefix comes from Björn Ottosson who designed the Oklab color space it's based on), Lightness, Chroma, Hue. It was added to CSS Color Level 4 and is now supported across all modern browsers.
oklch makes three things dramatically easier: building accessible color palettes that meet WCAG contrast ratios predictably, creating smooth gradients without the grey muddy middle, and generating color scales (like Tailwind's 50–950 scale) that actually look visually consistent. These are things that were genuinely hard in hsl.
The oklch Syntax Explained
oklch takes three values plus an optional alpha channel:
/* oklch(lightness chroma hue) */ oklch(0.7 0.15 145) /* oklch(lightness chroma hue / alpha) */ oklch(0.7 0.15 145 / 0.5) /* Percentage lightness also works */ oklch(70% 0.15 145) /* Named breakdown: Lightness: 0 (black) → 1 (white) [or 0% → 100%] Chroma: 0 (grey) → 0.4+ (vivid) [no upper bound, depends on display] Hue: 0 → 360 degrees (like hsl) Alpha: 0 (transparent) → 1 (opaque) [optional] */
Lightness (L): 0 to 1
Lightness in oklch runs from 0 (pure black) to 1 (pure white). Unlike hsl's 0%–100% lightness which is perceptually non-uniform, oklch lightness is calibrated to human vision — L: 0.5 genuinely looks like the midpoint between black and white regardless of what hue you're using.
Chroma (C): 0 to ~0.4
Chroma is the colorfulness or saturation of the color. At C: 0 the color is completely grey (the lightness determines how light or dark). As chroma increases, the color becomes more vivid. Unlike hsl's 0%–100% saturation, chroma in oklch has no theoretical upper bound — the maximum varies by hue and display capability. For sRGB displays, most colors cap out around C: 0.37. For wide-gamut P3 displays, you can go higher.
Hue (H): 0 to 360
Hue works like hsl — it's a degree on the color wheel. But the oklch color wheel is calibrated to perceptual uniformity too, so the hue degrees map differently to actual colors than in hsl:
- 0° — Red-pink
- 30° — Orange
- 90° — Yellow
- 145° — Green
- 200° — Cyan-teal
- 265° — Blue
- 300° — Purple
- 330° — Pink-magenta
/* Vivid red */ color: oklch(0.63 0.26 25); /* Warm orange */ color: oklch(0.75 0.19 55); /* Golden yellow */ color: oklch(0.87 0.18 90); /* Fresh green */ color: oklch(0.72 0.20 145); /* Sky blue */ color: oklch(0.70 0.18 210); /* Deep blue */ color: oklch(0.55 0.22 265); /* Purple */ color: oklch(0.60 0.22 295); /* Hot pink */ color: oklch(0.70 0.28 330); /* Near black */ color: oklch(0.15 0.02 265); /* Near white */ color: oklch(0.97 0.01 265); /* Pure grey */ color: oklch(0.60 0 0);
oklch vs hsl: The Key Difference
The fundamental difference is perceptual uniformity. Here's why it matters in practice. If you're building a button system and want a red, green and blue button all at the "same" brightness, hsl will fail you — the green will look lighter, the blue will look darker, even though they all have L: 50%. In oklch, L: 0.65 genuinely produces the same perceived brightness across all hues.
| Property | hsl | oklch |
|---|---|---|
| Perceptual uniformity | ✗ No — L looks different per hue | ✓ Yes — L is visually consistent |
| Accessible palette building | ✗ Hard — requires manual adjustment | ✓ Easy — set L once for all hues |
| Gradient smoothness | ✗ Grey muddy middle common | ✓ Smooth transitions naturally |
| Wide gamut color support | ✗ sRGB only | ✓ P3 and beyond (higher chroma) |
| Browser support (2026) | ✓ Universal | ✓ ~90% (Chrome 111+, FF 113+, Safari 15.4+) |
| Readability for humans | ✓ Intuitive hue/sat/light | ✗ Less intuitive — needs tooling |
| Design token systems | ✗ Inconsistent across hues | ✓ Excellent — predictable scale |
Converting from hex, rgb and hsl
There's no simple formula to convert hex or rgb to oklch in your head — the conversion goes through multiple color space transformations: sRGB → Linear sRGB → XYZ-D65 → Oklab → oklch. In practice, you use a tool.
Here are the oklch equivalents of common design colors as a reference:
/* Tailwind blue-500: #3b82f6 */
.blue-500 {
color: #3b82f6; /* fallback */
color: oklch(0.623 0.214 259.8); /* oklch equivalent */
}
/* Tailwind red-500: #ef4444 */
.red-500 {
color: #ef4444;
color: oklch(0.627 0.258 27.3);
}
/* Tailwind green-500: #22c55e */
.green-500 {
color: #22c55e;
color: oklch(0.723 0.219 149.6);
}
/* Pure white */
.white {
color: #ffffff;
color: oklch(1 0 0);
}
/* Pure black */
.black {
color: #000000;
color: oklch(0 0 0);
}
Use oklch.com for a visual oklch picker, or our Color Palette Generator which supports oklch output. You can also use the browser DevTools color picker in Chrome — click any color value and switch the format to oklch.
Building Accessible Color Palettes with oklch
This is where oklch truly shines. Building an accessible 10-step color scale (like Tailwind's 50–950) in hsl requires manually tweaking each step because hsl lightness isn't perceptually uniform. In oklch, you simply step the lightness value evenly and the result is visually consistent.
/* Brand color: purple, H=295, C=0.22 */
/* Just step the lightness from 0.95 → 0.15 */
:root {
--purple-50: oklch(0.95 0.04 295); /* near white tint */
--purple-100: oklch(0.90 0.07 295);
--purple-200: oklch(0.82 0.11 295);
--purple-300: oklch(0.74 0.15 295);
--purple-400: oklch(0.66 0.18 295);
--purple-500: oklch(0.60 0.22 295); /* base brand color */
--purple-600: oklch(0.52 0.21 295);
--purple-700: oklch(0.44 0.19 295);
--purple-800: oklch(0.35 0.16 295);
--purple-900: oklch(0.25 0.12 295);
--purple-950: oklch(0.15 0.07 295); /* near black shade */
}
/* Every step looks visually equidistant — no manual tweaking needed */
Checking contrast ratios with oklch
One of the most useful properties of oklch for accessibility is that lightness values map predictably to WCAG contrast ratios. As a rule of thumb:
- Text on white (
L: 1) needsL ≤ 0.55for AA compliance (4.5:1) - Large text on white needs
L ≤ 0.65for AA compliance (3:1) - Text on black (
L: 0) needsL ≥ 0.55for AA compliance
These aren't exact (chroma and hue affect perceived contrast slightly) but they give you a reliable starting point that's impossible to achieve with hsl.
oklch Gradients — Smoother Than Ever
CSS gradients between two saturated colors in hex or hsl often produce a grey or muddy middle — the gradient has to pass through unsaturated colours to get from one hue to another. oklch gradients interpolate through perceptually uniform space, producing smooth, vivid transitions without the muddy middle.
/* Old way: muddy middle */
.gradient-hsl {
background: linear-gradient(
to right,
hsl(150, 80%, 45%), /* green */
hsl(270, 80%, 55%) /* purple */
);
/* Passes through desaturated grey-blue in the middle */
}
/* New way: vivid all the way through */
.gradient-oklch {
background: linear-gradient(
in oklch to right,
oklch(0.65 0.20 145), /* green */
oklch(0.60 0.22 295) /* purple */
);
/* Stays vivid — interpolates through oklch space */
}
/* You can even control the hue interpolation direction */
.gradient-oklch-longer {
background: linear-gradient(
in oklch longer hue to right,
oklch(0.65 0.20 145),
oklch(0.60 0.22 295)
);
/* Goes the long way around the color wheel — through red and orange */
}
Adding in oklch to your gradient tells the browser to interpolate through oklch color space rather than the default sRGB. This is the key to vibrant gradients. You can use this with linear-gradient, radial-gradient and conic-gradient. Use our Gradient Generator to experiment visually.
oklch with CSS Custom Properties
oklch and CSS custom properties are a perfect pairing for design systems. You can define your palette as variables and then manipulate individual oklch channels using the oklch() function with variable channels.
:root {
/* Define brand hue and chroma once */
--brand-h: 265; /* blue-purple hue */
--brand-c: 0.22; /* chroma / saturation */
/* Generate the full scale using just lightness */
--color-50: oklch(0.96 calc(var(--brand-c) * 0.2) var(--brand-h));
--color-100: oklch(0.90 calc(var(--brand-c) * 0.4) var(--brand-h));
--color-200: oklch(0.82 calc(var(--brand-c) * 0.6) var(--brand-h));
--color-300: oklch(0.74 calc(var(--brand-c) * 0.8) var(--brand-h));
--color-400: oklch(0.66 var(--brand-c) var(--brand-h));
--color-500: oklch(0.58 var(--brand-c) var(--brand-h)); /* base */
--color-600: oklch(0.50 var(--brand-c) var(--brand-h));
--color-700: oklch(0.42 var(--brand-c) var(--brand-h));
--color-800: oklch(0.33 calc(var(--brand-c) * 0.9) var(--brand-h));
--color-900: oklch(0.24 calc(var(--brand-c) * 0.7) var(--brand-h));
--color-950: oklch(0.15 calc(var(--brand-c) * 0.5) var(--brand-h));
}
/* Change the entire brand color by updating just 2 variables */
.theme-teal {
--brand-h: 185;
--brand-c: 0.18;
}
.theme-red {
--brand-h: 25;
--brand-c: 0.26;
}
Dark Mode with oklch
Dark mode color systems are notoriously difficult in hsl — colors that look great in light mode often need completely different hue and saturation values in dark mode because hsl lightness isn't perceptually uniform. In oklch, you can often just flip the lightness scale and the colors remain consistent.
:root {
/* Light mode */
--bg: oklch(0.98 0.005 265); /* near white with blue tint */
--surface: oklch(0.94 0.008 265);
--text: oklch(0.15 0.015 265); /* near black */
--accent: oklch(0.55 0.22 265); /* brand blue */
--accent-hover: oklch(0.48 0.22 265);
}
@media (prefers-color-scheme: dark) {
:root {
/* Dark mode — just invert the lightness values */
--bg: oklch(0.12 0.015 265); /* near black with blue tint */
--surface: oklch(0.18 0.018 265);
--text: oklch(0.95 0.008 265); /* near white */
--accent: oklch(0.72 0.22 265); /* same hue + chroma, higher L */
--accent-hover: oklch(0.78 0.22 265);
}
}
/* Usage */
body {
background: var(--bg);
color: var(--text);
}
.btn-primary {
background: var(--accent);
color: var(--bg);
}
.btn-primary:hover {
background: var(--accent-hover);
}
Browser Support and Fallbacks
oklch is supported in Chrome 111+, Firefox 113+, Safari 15.4+ and Edge 111+. Global support is approximately 90% as of July 2026. Always provide a fallback for the remaining 10%.
/* Method 1: CSS cascade fallback */
.element {
color: #6c5ce7; /* fallback for old browsers */
color: oklch(0.55 0.22 265); /* modern browsers use this */
}
/* Method 2: @supports */
.element {
color: #6c5ce7;
}
@supports (color: oklch(0 0 0)) {
.element {
color: oklch(0.55 0.22 265);
}
}
/* Method 3: Custom properties with fallback */
:root {
--brand: #6c5ce7;
}
@supports (color: oklch(0 0 0)) {
:root {
--brand: oklch(0.55 0.22 265);
}
}
.element {
color: var(--brand); /* works in all browsers */
}
When using high chroma values (above ~0.37), you're entering wide gamut territory that only P3 displays can show. On sRGB displays, the browser will clamp the color to the nearest sRGB equivalent. This isn't harmful but be aware that your colors may look different on different screens. Stick to chroma ≤ 0.37 for safe sRGB-compatible colors.
Generate oklch color palettes visually
Use the Color Palette Generator to build and export oklch color scales. Preview your palette, copy the CSS variables and drop them straight into your design system.
Open Color Palette Generator →Frequently Asked Questions
What is oklch in CSS?
oklch is a CSS color function that defines colors using three perceptually uniform axes: Lightness (0–1), Chroma (color intensity, 0+), and Hue (0–360°). Unlike hsl, equal changes in oklch values produce visually equal changes in appearance, making it far superior for building accessible and consistent color systems.
What is the difference between oklch and hsl?
Both use lightness, colorfulness and hue. The key difference is perceptual uniformity. In hsl, two colors with the same lightness value can look very different in brightness (e.g. yellow vs blue). oklch corrects this — equal lightness values produce genuinely equal brightness. This makes oklch far better for accessible color systems and automatic palette generation.
What browsers support oklch?
Chrome 111+, Firefox 113+, Safari 15.4+ and Edge 111+. Global support is approximately 90% as of mid-2026. Always provide a hex or hsl fallback using the CSS cascade (declare the fallback first, then oklch on the next line).
How do I convert hex to oklch?
There's no simple mental calculation — the conversion goes through multiple color space transforms. Use oklch.com, the Chrome DevTools color picker (click any color → switch format to oklch), or our Color Palette Generator.
Should I use oklch in production today?
Yes, with a fallback. Declare your hex/hsl color first, then oklch on the next line. Modern browsers use oklch; older ones fall back to hex. With 90% browser support, it's safe for production — the same approach used when hsl was new.
What does "perceptually uniform" mean?
It means that equal numerical differences in the color model produce equal perceived differences to the human eye. For example, going from L: 0.5 to L: 0.6 in oklch looks like the same "step" of lightness regardless of which hue you're using. In hsl, the same lightness step looks very different depending on whether you're dealing with red, green, blue or yellow.
Can I use oklch in gradients?
Yes — and this is one of the best use cases. Use linear-gradient(in oklch to right, color1, color2) to interpolate through oklch space. This produces smooth, vivid gradients without the grey muddy middle that occurs when interpolating between saturated colors in sRGB. You can also use shorter hue or longer hue to control which direction around the color wheel the gradient travels.