For wide-gamut colour in CSS use color(display-p3 ...); for designing and manipulating colour, use oklch(). That split is the practical answer in 2026.
First, the support situation. color() works in Chrome and Edge 111 (March 2023), Firefox 113 (May 2023), and Safari 15, with global usage above 94%. Anything claiming “Firefox does not support it yet” is out of date.
This article covers the syntax of color() and then moves to what you will use far more often: oklch(), color-mix(), and relative colour syntax.
Sponsored
The syntax of color()
color() specifies a colour by naming the colour space the coordinates belong to. The three RGB values are written in the range 0–1, not 0–255.
color(<colour-space> <v1> <v2> <v3> / <alpha>)
.button {
background-color: color(display-p3 1 0.5 0); /* vivid orange */
border-color: color(display-p3 0.5 0.2 0.8 / 0.6);
}
| Colour space | Characteristics | Typical use |
|---|---|---|
srgb |
The traditional web space | Compatibility |
srgb-linear |
sRGB without gamma correction | Compositing and blending maths |
display-p3 |
About 25% wider than sRGB | The only one that matters on the web |
rec2020 |
Very wide gamut | Video production |
a98-rgb |
Adobe RGB equivalent | Photography and print |
prophoto-rgb |
Extremely wide | Photo editing |
xyz / xyz-d50 / xyz-d65 |
Device-independent reference | Intermediate in conversions |
For web work, display-p3 is effectively the only useful option. Specifying rec2020 or prophoto-rgb is pointless when almost no display can render them.
Do you actually need a fallback?
Yes—but the reason is unsupported displays more than unsupported browsers.
On a monitor that cannot show wide gamut, display-p3 colours are automatically clipped into sRGB. Nothing breaks, but you do not get the vividness you designed for, and the clipping can shift the hue slightly away from your intent.
The simplest form is two declarations:
.button {
background-color: rgb(255 128 0); /* unsupported browsers */
background-color: color(display-p3 1 0.5 0); /* overrides where supported */
}
To avoid clipping entirely and only go vivid on capable displays, branch with @media (color-gamut: p3).
.button {
background-color: rgb(255 128 0);
}
@media (color-gamut: p3) {
.button {
background-color: color(display-p3 1 0.5 0);
}
}
Written this way, the P3 colour is used only when the display can actually produce it. That is the safer choice when small differences in a brand colour matter.
Sponsored
oklch() is what you will use day to day
color() exists to widen the gamut, but for everyday UI design the useful function is oklch(). It keeps the ergonomics of hsl() while fixing its worst flaw.
oklch(<lightness> <chroma> <hue> / <alpha>)
/* example */
--brand: oklch(0.65 0.2 250); /* L 65%, C 0.2, hue 250deg (blue) */
The flaw in hsl() is that the same lightness value looks completely different depending on hue. hsl(60 100% 50%) yellow and hsl(240 100% 50%) blue are nominally the same lightness, and the yellow is dramatically brighter.
oklch() is perceptually uniform, so the lightness number matches perceived brightness. That makes the following safe:
:root {
--hue: 250;
/* vary only lightness: the brightness ladder survives a hue change */
--c-100: oklch(0.95 0.03 var(--hue));
--c-300: oklch(0.80 0.10 var(--hue));
--c-500: oklch(0.65 0.20 var(--hue));
--c-700: oklch(0.50 0.16 var(--hue));
--c-900: oklch(0.30 0.10 var(--hue));
}
Changing --hue swaps the entire theme colour while preserving the contrast relationships. Do the same in hsl() and some hue values will produce unreadable text.
oklch() can also express colours outside sRGB, so wide-gamut displays render them more vividly automatically. If you want both wide gamut and workable ergonomics, oklch() is the more practical choice than color(display-p3 ...).
Which colour function to use
| Function | You specify | Choose it when |
|---|---|---|
rgb() / hex |
Red, green, blue | Pasting values from a design tool |
hsl() |
Hue, saturation, lightness | Little reason to choose it now |
color() |
Colour space plus coordinates | Naming a wide-gamut colour explicitly |
lab() / lch() |
Perceptual lightness, chroma, hue | Print, colour-difference maths |
oklch() |
Improved perceptual coordinates | Designing UI palettes |
color-mix() |
A blend of two colours | Hover states, tints |
Sponsored
Generating hover colours with color-mix()
Defining two colours just so hover can be slightly darker is tedious. color-mix() derives one from the other.
.button {
--bg: oklch(0.65 0.2 250);
background-color: var(--bg);
}
.button:hover {
/* mix 12% black into the base */
background-color: color-mix(in oklab, var(--bg), black 12%);
}
.button:disabled {
/* fade it towards the page background */
background-color: color-mix(in oklab, var(--bg) 30%, canvas);
}
Specify in oklab as the interpolation space. Mixing in srgb muddies the midpoint towards grey. The same applies to gradients:
/* muddy in the middle */
background: linear-gradient(to right, blue, yellow);
/* stays vivid through the middle */
background: linear-gradient(in oklab to right, blue, yellow);
Relative colour syntax
The from keyword lets you decompose an existing colour and rewrite part of it. Combined with custom properties, theme definitions get dramatically shorter.
:root {
--brand: oklch(0.65 0.2 250);
}
.card {
/* a pale background: same hue, higher lightness */
background: oklch(from var(--brand) 0.95 c h);
/* a desaturated border */
border-color: oklch(from var(--brand) l calc(c * 0.4) h);
/* the complement: rotate hue by 180 degrees */
--accent: oklch(from var(--brand) l c calc(h + 180));
}
l, c, and h act as variables holding the original components. You can derive an entire palette from a single brand colour, which makes theme switching far simpler to implement.
Support and feature detection
| Feature | Chrome / Edge | Firefox | Safari |
|---|---|---|---|
color() |
111 | 113 | 15 |
oklch() / oklab() |
111 | 113 | 15.4 |
color-mix() |
111 | 113 | 16.2 |
| Relative colour syntax | 119 | 128 | 16.4 |
If you must support older environments, branch with @supports:
.button { background-color: #ff8000; }
@supports (background-color: oklch(0.65 0.2 250)) {
.button { background-color: oklch(0.65 0.2 250); }
}
DevTools colour pickers let you switch colour spaces when checking your work. You cannot see the difference on an sRGB monitor, so do not conclude “nothing changed” from a review on the wrong display.
Summary
color()works in Chrome/Edge 111, Firefox 113, and Safari 15. “Firefox does not support it” is outdated- Only
display-p3is useful on the web, and values run 0–1 - Unsupported displays clip to sRGB.
@media (color-gamut: p3)is the precise way to branch - Design palettes with
oklch()—its lightness value matches perceived brightness - Derive hover and tint colours with
color-mix(in oklab, ...);in srgbmuddies them - Relative colour syntax,
oklch(from var(--brand) ...), generates a palette from one colour
Use color() when the goal is a wider colour, and oklch() when the goal is designing colour. That split removes the confusion. For state-driven styling, pair it with the CSS :has() selector.