CSS color recipe
oklch() Colors you can reason about, with exact hex fallbacks
OKLCH writes a color as lightness, chroma and hue, so a darker hover is a lower L and four status colors can share one lightness. Keep the values in custom properties, and give older browsers the same colors as hex.
- Baseline
- Widely available
- First versions
- Chrome 111 · Firefox 113 · Safari 15.4
- Tested
- 3 of 3 engines
Live example
The recipe, running
Left: the CSS below, as your browser runs it. Right: the same CSS as a browser without the feature reads it, so you see exactly what the fallback gives.
Turn on JavaScript to see whether your browser supports it; the examples work either way.Your browser supports it, so the left example runs the modern path.Your browser does not support it yet, so both examples show the fallback.
The CSS
Copy it as it is
This is the exact CSS the example runs. Fallbacks come first; the modern values follow, so each browser keeps the last line it understands.
oklch.css
:root {
--brand: #3959DA;
--brand-strong: #253DAD;
--tint: #EDF2FD;
--ok: #429C5A;
--warn: #B77610;
--bad: #C8635D;
--info: #248FCC;
}
@supports (color: oklch(0% 0 0)) {
:root {
--brand: oklch(52% 0.2 268);
--brand-strong: oklch(42% 0.18 268);
--tint: oklch(96% 0.016 268);
--ok: oklch(62% 0.13 150);
--warn: oklch(62% 0.13 70);
--bad: oklch(62% 0.13 25);
--info: oklch(62% 0.13 240);
}
}
.card {
padding: 16px;
border: 2px solid var(--brand);
border-radius: 12px;
background: var(--tint);
}
.button {
padding: 10px 18px;
border: 0;
border-radius: 8px;
background: var(--brand);
color: #ffffff;
font: inherit;
}
.button:hover { background: var(--brand-strong); }
.dots { display: flex; gap: 10px; margin-top: 14px; }
.dot { width: 40px; height: 40px; border-radius: 50%; }
.dot--ok { background: var(--ok); }
.dot--warn { background: var(--warn); }
.dot--bad { background: var(--bad); }
.dot--info { background: var(--info); }
How it works
What each part does
Hex first, OKLCH inside
@supports. A custom property accepts any value, so an old browser would keep anoklch()it cannot read and paint nothing. The@supportsblock only redefines the properties whereoklch()works.The fallbacks are exact. Each OKLCH value here sits inside sRGB, so its hex is the same color, not an approximation.
One lightness, four hues. The status dots share L = 62%, so none of them looks heavier than the others.
Browser support
Where it runs
First version of each browser with the feature, from the web-features project and MDN’s browser-compat-data. “No” means not supported yet.
- oklch() and oklab()Widely availableBaseline widely available since 9 November 2025.
| Browser | oklch() and oklab() |
|---|---|
| Chrome | 111 |
| Chrome for Android | 111 |
| Edge | 111 |
| Firefox | 113 |
| Firefox for Android | 113 |
| Safari | 15.4 |
| Safari on iOS | 15.4 |
Support reviewed on against web-features 98723fd and browser-compat-data 3b28abe.
Tested
Tested in three engines
| Engine | Version | Supported | As written | Fallback |
|---|---|---|---|---|
| Chromium | 147.0.7727.15 | Yes | Pass | Pass |
| Firefox | 148.0.2 | Yes | Pass | Pass |
| WebKit | 26.4 | Yes | Pass | Pass |
Run on in Chromium 147.0.7727.15, Firefox 148.0.2 and WebKit 26.4, with Playwright.
Accessibility
Measured, not assumed
White on the brand color measures 5.81:1, enough for body text at AA.
- 5.81:1AA
#FFFFFF / #3959DA
References
Go to the source
- CSS Color Module Level 4: OKLab and OKLCHwww.w3.org
- Björn Ottosson: A perceptual color space for image processingbottosson.github.io
- web-features at 98723fdgithub.com
- MDN browser-compat-data at 3b28abegithub.com