CSS color gamut: sRGB, Display P3, Rec2020, and OKLCH compared

5 min read

For most of the web’s history, CSS colors were locked to sRGB (hex / rgb / hsl). With CSS Color Module Level 4 widely shipped and Display P3 ubiquitous on modern devices, wider gamuts are finally available. This article maps the gamut sizes, the CSS syntax for each, browser/display support, and where OKLCH fits in.

What “gamut” means

A color gamut is the set of colors a color space can represent. Wider gamut → more vivid colors, but only if the display can show them.

Approximate coverage on the CIE 1931 chromaticity diagram (rough percentages of the visible spectrum):

GamutCoverageUsed by
sRGB35%classic web, HD video
Display P345%iPhone, Mac, modern phones
Rec202075%UHD/4K HDR video
Rec2100same as Rec2020HDR10, HLG

These are approximate fractions of the full visible spectrum each space can express. No consumer display reproduces full Rec2020, but Display P3 is now standard on Apple devices and most flagship Android phones.

CSS syntax

Legacy: sRGB-locked

color: #ff0000;
color: rgb(255, 0, 0);
color: hsl(0, 100%, 50%);

All sRGB. Cannot express Display P3 reds — value 255 caps you at sRGB’s gamut boundary.

color() function: explicit color space

color: color(display-p3 1 0 0);
color: color(rec2020 1 0 0);

color() takes a color-space identifier. display-p3 (1, 0, 0) is a more saturated red than sRGB (1, 0, 0).

oklch(): perceptually uniform

color: oklch(70% 0.2 30);

OKLCH (Oklab in polar coordinates) is a perceptually uniform color space.

  • L (lightness): 0% – 100%.
  • C (chroma / saturation): 0 – ~0.4.
  • H (hue): 0deg – 360deg.

OKLCH is a representation, not a gamut. With high C, OKLCH values can land outside sRGB into Display P3 or Rec2020 territory.

display-p3 keyword (CSS Color 5)

color: rgb(from #ff0000 r g b / display-p3);

CSS Color 5’s from-syntax color-space conversion exists in the spec but ships unevenly. Don’t depend on it yet.

Why OKLCH is the recommended default

hsl(0deg, 100%, 50%) reads naturally but lightness is not consistent across hues:

hsl(0deg, 100%, 50%)    /* pure red */
hsl(60deg, 100%, 50%)   /* yellow — appears blindingly bright */
hsl(240deg, 100%, 50%)  /* pure blue — almost black */

HSL’s “50% lightness” is a math value; perceived brightness varies enormously by hue. OKLCH fixes this:

oklch(70% 0.2 0)    /* 70% lightness red */
oklch(70% 0.2 60)   /* 70% lightness yellow — visually equal weight */
oklch(70% 0.2 240)  /* 70% lightness blue — same */

A “lightness 70%, hue varies” set produces visually-balanced colors. Designing palettes by hand becomes practical.

Display reality

DeviceGamutNotes
iPhone 7+Display P3nine years of consecutive support
MacBook 2015+Display P3iMac 5K and onward
iPad ProDisplay P3iPad Air 4+ also
Flagship Android (Galaxy S, Pixel)Display P3varies by manufacturer
High-end PC monitorsDisplay P3 / Adobe RGBEizo, Apple Studio Display
Mainstream PC monitorssRGB (70-99%)many 4K panels still sRGB-only

When the display lacks Display P3 support, browsers gamut-map the P3 color to the nearest sRGB representation. The color shows, but the saturation gain is lost.

color-gamut media query

Branch on display capability:

.brand-red {
	color: #ff0000;
}

@media (color-gamut: p3) {
	.brand-red {
		color: color(display-p3 1 0 0);
	}
}

@media (color-gamut: rec2020) {
	.brand-red {
		color: color(rec2020 1 0 0);
	}
}

“Show the saturated P3 red on P3 displays; fall back to sRGB hex elsewhere.”

Use-case guidance

Brand and accent colors

Use OKLCH or Display P3 with an sRGB fallback:

.btn-primary {
	background: #c84b1f; /* sRGB fallback */
	background: color(display-p3 0.78 0.3 0.13); /* P3 boost */
}

CSS uses the last understood declaration, so browsers that support color() get P3; older ones get the sRGB hex.

Whole design system

Standardize on OKLCH with CSS custom properties:

:root {
	--color-red-500: oklch(60% 0.2 25);
	--color-red-400: oklch(70% 0.18 25);
	--color-red-600: oklch(50% 0.2 25);
}

Even lightness steps across the palette become trivial to maintain. Hand-designing Tailwind-like 50/100/…/900 palettes becomes feasible.

Photos and video

Embed color profiles in the asset, not the CSS. Browsers honor the ICC profile and render correctly per display.

Browser support (2026)

FeatureChromeFirefoxSafari
color(display-p3 ...)✓ (111+)✓ (113+)✓ (15+)
color(rec2020 ...)✓ (111+)✓ (113+)✓ (15+)
oklch() / oklab()✓ (111+)✓ (113+)✓ (15.4+)
@media (color-gamut)
color-mix()✓ (111+)✓ (113+)✓ (16.2+)

All major browsers support the core features. IE / legacy is unsupported and assumed dropped.

Implementation pitfalls

1. sRGB values on P3 displays may shift

Some browsers extend sRGB values into P3 gamut on P3 displays automatically. Colors look more saturated than authored. Specify the color space explicitly when the exact appearance matters.

2. Screenshots lose Display P3

Pasting a P3-colored screenshot into a tool that handles sRGB only causes the colors to shift. This is a tooling/color-management issue, not a CSS bug.

3. Print is sRGB-or-CMYK territory

A vivid Display P3 red can’t be printed faithfully. If brand colors must work in print, define them in Adobe RGB or CMYK and use sRGB for the web port.

Summary

CSS color went from sRGB-locked to color() + oklch() with explicit gamuts. The 2026 default is brand colors in Display P3 with sRGB fallback, design system tokens in OKLCH, fallback hex everywhere. @media (color-gamut) lets you ship the saturation boost only where the display can render it. Older monitors get the safe sRGB rendering, new ones get the vivid one.

For converting between hex / rgb / OKLCH and inspecting gamut boundaries, the color converter tool covers the standard cases.