OKLCH in CSS: A Practical Guide for Developers (2026)
OKLCH in CSS gives you perceptually uniform color, wider P3 gamut, and predictable lightness. Here is the syntax, the math, and the fallbacks for 2026.
OKLCH in CSS: A Practical Guide for Developers
If you have ever set two HSL colors to the same lightness and watched one look washed out while the other glowed, you have hit the reason OKLCH exists. OKLCH is a CSS color function that describes color the way your eye actually reads it — as lightness, chroma, and hue — while unlocking the wider gamut modern screens can show. This guide covers the syntax, the math behind each channel, and safe fallbacks for 2026.
TL;DR
- OKLCH sets color by lightness (L), chroma (C), and hue (H) in a perceptually uniform space.
- Same lightness value = same perceived brightness across hues, unlike HSL.
- Syntax is
oklch(L C H / alpha)— space-separated, no commas, nooklcha(). - It reaches wide-gamut P3 colors that HEX, RGB, and HSL cannot express.
- Baseline "widely available" since May 2023; works in every current browser.
What is OKLCH in CSS?
OKLCH is a CSS color notation added in CSS Color Module Level 4. It is the cylindrical form of the Oklab color space: same lightness axis, but the two Cartesian coordinates of Oklab are swapped for the more intuitive chroma and hue. In other words, OKLCH is to Oklab what HSL is to RGB — the same underlying model, wrapped in coordinates a human can reason about.
The three channels explained
An OKLCH color is three numbers. Each maps to a question you already ask when choosing a color.
| Channel | Range | Controls |
|---|---|---|
| L (lightness) | 0%–100% (or 0–1) | How light or dark, 0% is black |
| C (chroma) | 0 to ~0.4 | How saturated, 0 is grey |
| H (hue) | 0–360 | Position on the color wheel |
Lightness runs from pure black at 0% to pure white at 100%. Chroma is the intensity or purity of the color, starting at 0 for grey. Hue is the familiar angle around the color wheel, written as a plain number with no deg unit.
OKLCH vs Oklab: what is the difference?
Both describe the same colors. Oklab uses lightness plus two axes — a (green–red) and b (blue–yellow) — which are precise but hard to picture. OKLCH keeps the lightness axis and converts a and b into a distance from the center (chroma) and an angle (hue). If you have ever struggled to guess what oklab(0.7 -0.1 0.15) looks like, that is why most people reach for oklch() instead.
Why the "Ok" prefix matters
There are also older lch() and lab() functions in the spec. They target the same wide gamut, but they carry a known flaw: the hue drifts slightly as you change lightness or chroma, so a "blue" can wander toward purple as it darkens. As the Evil Martians team documented, Björn Ottosson's Oklab model corrected that drift. The Ok prefix is the signal that hues stay put. Prefer oklch() over lch() for this reason.
OKLCH syntax: lightness, chroma, hue
The formal grammar in the spec looks dense, but the everyday form is short. Lightness first, then chroma, then hue, separated by spaces.
/* lightness chroma hue */
color: oklch(70.9% 0.195 47); /* a warm orange */
color: oklch(0.629 0.258 29.2); /* a vivid red */
Lightness accepts either a percentage (70.9%) or the equivalent decimal (0.709). Chroma is always a decimal. Hue is a bare number of degrees.
No commas, and no oklcha()
Level 4 dropped the comma-separated legacy style. Values are separated by whitespace, and there is no separate alpha function. The same modernization applies to the older functions too:
/* legacy */ hsla(26deg, 99%, 51%, 0.75)
/* modern */ hsl(26 99% 51% / 0.75)
Because the new syntax handles transparency inline, oklcha() was never created — oklch() does it all.
Setting alpha with the slash
Add opacity with a / after the hue:
.overlay {
background: oklch(0.2 0.03 250 / 0.6);
}
Everything before the slash defines the color; everything after sets alpha from 0 to 1 (or a percentage).
Why is OKLCH better than HSL?
HSL has been the friendly color function for a decade, and it is still easy to read. The problem is that its lightness lies. OKLCH fixes the two issues that make HSL palettes feel uneven.
HSL's inconsistent lightness problem
In HSL, lightness is a geometric midpoint, not a perceptual one. Set yellow and blue to hsl(H 100% 50%) and the yellow looks far brighter than the blue, even though both claim 50% lightness. This wrecks any system that relies on lightness for hierarchy — think text-on-background contrast or a set of status colors that should feel equally strong. OKLCH lightness is tuned to perception, so oklch(0.7 0.15 H) reads at roughly the same brightness whatever the hue.
The dead grey zone in gradients
Interpolating between two saturated HSL colors often passes through a muddy grey in the middle — the "dead grey zone" that Geoff Graham describes in Smashing Magazine. Because OKLCH travels around the hue wheel instead of straight through the desaturated center, gradients stay vivid end to end — the same principle behind the iKit Gradient Generator. You can prove it to yourself in one rule:
.bar {
background: linear-gradient(
to right in oklch,
oklch(0.7 0.2 250),
oklch(0.7 0.2 30)
);
}
Perceptual uniformity in plain terms
"Perceptually uniform" means a fixed change in a number produces a roughly equal-looking change in the color. Nudge lightness by 0.05 and the shift looks the same whether you started at a red or a green. That predictability is what makes OKLCH ideal for generating tint-and-shade ramps programmatically — hold chroma and hue, step lightness evenly, and you get a balanced scale instead of one that jumps around, which is exactly how a good color palette generator builds an even set. If you are building a palette by hand, the iKit Color Picker & Converter lets you read the same swatch as OKLCH, HEX, and RGB side by side so you can sanity-check the conversion.
How to use OKLCH with wide-gamut P3 displays
The other reason to switch is reach. HEX, RGB, and HSL can only address colors inside the sRGB gamut. OKLCH can point at colors beyond it.
sRGB vs Display P3
Most laptops and phones sold in the last few years ship P3-capable screens. Display P3 covers roughly 25% more color than sRGB, with noticeably more saturated reds and greens. A color like oklch(0.65 0.28 25) may sit outside sRGB entirely — a red more vivid than #ff0000. On a P3 screen the browser renders the extra saturation; on an sRGB screen it clamps down to the nearest displayable color automatically.
Checking chroma limits
There is no hard ceiling on chroma, but a practical one. Per the CSS Color 4 spec and the Evil Martians write-up, real-world chroma stays under about 0.37, and the true maximum varies by hue and by display gamut. Push chroma past what the gamut allows and the value is gamut-mapped back into range — so it never breaks, it just stops getting more saturated.
Feature-detecting P3 support
If you want the vivid version only where it renders, gate it behind a media query:
.cta { background: oklch(0.6 0.18 250); }
@media (color-gamut: p3) {
.cta { background: oklch(0.6 0.28 250); }
}
Browser support and fallbacks for OKLCH
This is usually the blocker for adopting a color feature, and here the answer is easy.
Is OKLCH ready for production in 2026?
Yes. According to web.dev's Baseline 2023 report, oklch() reached Baseline "widely available" in May 2023 after landing in Chrome 111, Safari 15.4, and Firefox 113. In 2026 that means every current browser parses it natively, and browser devtools show OKLCH sliders in their color pickers. The MDN reference for oklch() tracks the current support table.
Writing a safe fallback
For the rare legacy browser, the cascade does the work. Declare an sRGB color first, then override it — browsers that do not understand the second line simply keep the first:
.btn {
color: #f8a100; /* fallback */
color: oklch(0.75 0.17 60); /* modern */
}
Prefer an explicit @supports test when you need certainty:
@supports (color: oklch(0 0 0)) {
.btn { color: oklch(0.75 0.17 60); }
}
Relative colors and color-mix()
OKLCH pairs well with two newer features. Relative color syntax lets you derive a color from another — oklch(from var(--brand) l c h / 0.5) reuses a brand color's channels and changes only the parts you name. And color-mix(in oklch, ...) blends two colors through the perceptually uniform space, which is the cleanest way to generate hover and active states. Both are part of the ongoing Color Level 5 work and are widely supported in 2026.
References
- CSS Color Module Level 4 — W3C spec; source for the
oklch()grammar, channel ranges, and modern slash-alpha syntax. - oklch() CSS function — MDN — channel definitions, relative color examples, and the live browser-support table.
- Baseline 2023 — web.dev — confirms
oklch()reached Baseline "widely available" in May 2023. - Falling For Oklch — Smashing Magazine — gamuts vs color spaces, the dead grey zone, and the practical 0.37 chroma limit.
- OKLCH in CSS: why we moved from RGB and HSL — Evil Martians — the hue-shift flaw in LCH/LAB that Oklab corrects.
Related on iKit
- See any HEX code broken down into its RGB components — once OKLCH gamut-maps a color back to sRGB, this shows the exact HEX-to-RGB math behind the fallback value.
- Turn RGB values back into a HEX string the right way — the reverse conversion, handy for writing the sRGB fallback line that sits above your
oklch()declaration.
Related posts
WCAG Contrast Ratios: How AA and AAA Are Calculated (2026)
A developer's guide to the WCAG contrast ratio formula, the 4.5:1 AA and 7:1 AAA thresholds, large-text exceptions, and how to check color contrast in code.
RGB to HEX: The 2026 Guide for Designers and Developers
RGB to HEX is base-10 to base-16 on three bytes. Here's the exact math, the JavaScript one-liners, alpha channels, and the mistakes that trip people up.
Why str.length Lies About Emoji in JavaScript (2026)
In JavaScript str.length counts UTF-16 code units, so one emoji reports 2 and a family emoji reports 11. Here is how to count emoji characters correctly.