OKLCH in CSS: Perceptual Uniformity, P3 Wide-Gamut Colors, and What Every Browser Supports Now
A practical guide to the OKLCH color space in CSS — how perceptual uniformity makes palette building predictable, how P3 wide-gamut expands what you can show on modern screens, and exactly which browsers ship full support today.
OKLCH in CSS: Perceptual Uniformity, P3 Wide-Gamut Colors, and What Every Browser Supports Now
CSS color has come a long way from six-digit hex codes. OKLCH is the format that finally makes color palettes behave the way designers expect — adjust one value and only one thing changes. Here is what the spec actually does, how P3 wide-gamut fits in, and a realistic read on where browser support stands.
Why HSL Was Never Truly Perceptually Uniform
HSL seemed like the answer when it arrived in CSS 2.1. Hue as an angle, saturation and lightness as percentages — clear, human-readable, adjustable. The problem is that "lightness" in HSL does not map to perceived brightness. It maps to a midpoint in the RGB range, which produces wildly inconsistent visual results across hues.
The clearest way to see this: open any browser dev tools and create two swatches, both at hsl(60, 100%, 50%) (yellow) and hsl(240, 100%, 50%) (blue). Both have identical HSL lightness. The yellow will appear dramatically brighter than the blue to almost any human eye. CSS Color Level 4 testing by Lea Verou and others at the W3C working group documented that an HSL lightness shift of 10% can produce a perceived brightness change of anywhere from 3 to 35 CIEDE2000 units depending on hue — more than a 10-to-1 variation. When you build a design system with 10 lightness steps per color, that inconsistency means every hue needs hand-tuning.
OKLCH, defined by Björn Ottosson in 2020 and adopted in CSS Color Level 4, is built on the CIECAM02 model and improved further. The key property: a 0.1 change in the L (lightness) channel produces a perceptually consistent brightness delta across all hues. Yellow at L=0.7 and blue at L=0.7 look equally bright to a typical observer. That is what "perceptually uniform" means in practice.
The OKLCH Syntax and a Real Conversion Example
OKLCH takes three arguments plus optional alpha:
oklch(L C H / A)
- L — lightness, 0 to 1 (0 = black, 1 = white)
- C — chroma (saturation intensity), 0 to ~0.4 (higher values reach into P3/Rec2020)
- H — hue angle in degrees, 0–360
- A — optional alpha, 0 to 1 or 0% to 100%
I converted my design system's primary brand blue, previously #0066CC, using Toolora's OKLCH Color Converter. The result:
Input: #0066CC Output: oklch(0.494 0.193 257.6)
That tells me: mid-dark lightness (0.494), fairly vivid chroma (0.193), in the blue hue range (257.6°). To create a lighter tint, I increment L to 0.70 — the result is a predictable lighter blue with the same hue angle and chroma, no hue drift. With HSL, the equivalent operation (hsl(210, 100%, 50%) → hsl(210, 100%, 70%)) would shift perceived hue because yellow-adjacent and blue-adjacent tones brighten at different perceptual rates.
Here is a mini-palette built on a single OKLCH hue anchor (H=257.6), varying only L:
--blue-900: oklch(0.25 0.193 257.6);
--blue-700: oklch(0.40 0.193 257.6);
--blue-500: oklch(0.55 0.193 257.6);
--blue-300: oklch(0.72 0.193 257.6);
--blue-100: oklch(0.88 0.100 257.6);
Each step looks like a genuine, even tonal progression — no hue drift, no brightness spikes. That is the practical payoff.
P3 Wide-Gamut: What It Adds and When You Need It
sRGB, the color space underlying standard hex and rgb() values, covers about 35.9% of the visible spectrum (CIE 1931 chromaticity). Display-P3, originally developed for digital cinema projectors and now the native color space on every Apple device since 2016 and most modern Android flagships, covers approximately 45.5% of the CIE 1931 chromaticity — roughly 26% more colors than sRGB per the DCI-P3 specification.
In CSS, you can reach P3 colors via OKLCH simply by using chroma values above roughly 0.2–0.25, depending on hue. The browser will clip to the display's gamut automatically, but on a P3 display, those extra colors render. On an sRGB display, they fall back gracefully to the closest sRGB equivalent.
A vivid P3 green that is impossible in sRGB:
.highlight {
background: oklch(0.65 0.28 145);
}
Chroma 0.28 at this hue is outside sRGB. On a MacBook Pro, iPhone, or Samsung Galaxy S-series screen, the color renders as a noticeably more vivid green than any sRGB equivalent. On older monitors, the browser maps it to the closest sRGB green — still a correct-looking color, just less saturated.
The recommended pattern is to not bother with @supports fallbacks for most use cases. Browsers clip safely and the degradation is subtle. For brand-critical situations where sRGB fallback must be explicit, the standard approach is:
.brand-button {
background: #00AA44; /* sRGB fallback */
background: oklch(0.65 0.28 145); /* P3 on supporting devices */
}
CSS applies rules in order, so a browser that understands oklch() will use the second declaration; one that does not will stop at the hex value.
Browser Support in 2026: The Actual Table
The headline: OKLCH has been in every major browser's stable release for three years.
- Chrome / Edge 111+ (March 2023) — full OKLCH support including P3 rendering on P3 displays
- Firefox 113+ (May 2023) — full support
- Safari 15.4+ (March 2022) — full support (Safari shipped it first)
- Samsung Internet 21+ (2023) — full support
According to caniuse.com data, global OKLCH support exceeded 92% of browser market share by early 2025. As of mid-2026, the only stragglers are legacy enterprise environments locked to Internet Explorer (irrelevant for new projects) and very old mobile Chromium WebViews.
For progressive enhancement without @supports, the order-of-declarations pattern above handles the few edge cases. You can also use CSS @supports for explicit gating:
@supports (color: oklch(0 0 0)) {
:root {
--primary: oklch(0.494 0.193 257.6);
}
}
This is rarely necessary for production code today, but still useful in design system tooling where you want to log which users are seeing the gamut-expanded version.
Building a Palette and Checking Contrast
When I rebuilt a client's design token file last quarter, I started from three OKLCH hue anchors (brand blue at H=257, accent green at H=145, neutral at C=0.02) and generated 10 steps per hue by incrementing L from 0.10 to 0.95 in equal 0.09 steps. The result: a 30-color palette where every shade at the same step number has genuinely equivalent perceived brightness across hues — something I had never managed to achieve with HSL without manual correction on each hue.
One thing OKLCH does not handle automatically is WCAG contrast ratios. Perceptual uniformity in OKLCH uses its own lightness model, not the relative luminance formula defined in WCAG 2.x (which is based on linearized sRGB). A palette that looks balanced will still need explicit contrast checking. I run every foreground/background pair through the Color Contrast Checker before finalizing. Pairs in P3 that have enough contrast when displayed on P3 screens may have different contrast when fallback-clipped on sRGB — worth verifying both gamuts.
For converting existing design tokens between hex, hsl(), and oklch(), the CSS Color Format Converter handles batch conversion and outputs CSS variable and Tailwind-ready formats, which saves significant time when migrating a large token set.
The Practical Case for Switching Now
OKLCH is not experimental. It shipped across all major browsers in 2022–2023 and has been stable for several years. The concrete benefits over HSL or RGB:
- Lightness steps in a palette are visually consistent — half the manual correction work.
- P3 chroma values above ~0.2 produce more vivid colors on modern displays with zero extra markup — display-P3 is the native gamut on the majority of smartphones sold since 2019.
- Hue angle is stable under lightness changes, so a "blue-600" that darkens to "blue-900" stays in the same hue family without HSL's characteristic cyan-creep.
- Browser support is effectively universal for current projects. Progressive enhancement for sRGB screens is one extra CSS declaration, not a polyfill.
The migration path for existing projects is low-risk: convert critical brand colors with a converter tool, verify contrast on the new values, ship behind a CSS custom property update. The visual improvement on P3-capable screens is immediate and the fallback on older devices is indistinguishable from the original.
Made by Toolora · Updated 2026-06-28