Short answer
Write every colour as an sRGB value first, then override it with a wide-gamut value — color(display-p3 …), oklch() or lab() — either in a later declaration or inside @media (color-gamut: p3). Browsers that do not understand a colour function drop that declaration and keep the earlier one; browsers that do understand it on an sRGB screen gamut-map it. CSS Color 4 supplies the new functions and spaces, CSS Color 5 adds color-mix() and relative colour syntax, and Media Queries 4 supplies the gamut test.
Before CSS Color 4, every CSS colour was sRGB. Level 4 keeps HEX, rgb(), hsl(), hwb() and named colours as sRGB and adds functions that carry their own colour space: lab() and lch() (CIE, D50-relative), oklab() and oklch() (Ottosson's OKLab, D65-relative), and color() with predefined spaces — srgb, srgb-linear, display-p3, display-p3-linear, a98-rgb, prophoto-rgb, rec2020, xyz-d50 and xyz-d65. It also defines the none keyword for missing components, how to interpolate between colours in a chosen space, and how to gamut-map colours a display cannot show. oklch() and the color-gamut media feature have been usable in all major browsers since 2023.
The simplest pattern relies on the CSS rule that an unrecognised value invalidates only its own declaration: write background: #00a86b; followed by background: oklch(0.65 0.2 155);. Old browsers use the first; newer ones the second, gamut-mapping it on sRGB screens. That is enough when you are happy for the browser's gamut mapping to choose the sRGB version. When you want to control the sRGB version, or keep sRGB screens on a hand-picked colour, put the wide-gamut value inside @media (color-gamut: p3) { … }. With custom properties, define the token once in sRGB and redefine it inside the media query, so every component inherits the right value without duplicated rules. @supports (color: oklch(0 0 0)) is useful when the choice depends on syntax support rather than the screen.
color-gamut: p3 means the browser and output device together can show approximately the P3 gamut or more; it does not mean the page is being colour-managed perfectly, and it is true on screens that only approximately cover P3. The query is a minimum: rec2020 screens also match p3. It says nothing about HDR, which has its own dynamic-range query. And it describes the screen the page is on now, so moving a window between an external sRGB monitor and a laptop's P3 panel can switch styles. Always design the p3 branch as an enhancement of a complete sRGB design, not as the primary one.
CSS Color 5, still a working draft, adds two tools that change how palettes are written. color-mix(in oklab, var(--brand) 70%, white) blends two colours in a named space — OKLab by default — which gives more even tints than mixing in sRGB. Relative colour syntax, such as oklch(from var(--brand) calc(l + 0.1) c h), takes an existing colour apart and rebuilds it with adjusted components, so hover and disabled states can be derived from a single token. Both are widely implemented, but results depend on the interpolation space and on gamut mapping, so derived colours should be checked for contrast rather than assumed.
| Pattern | Example | Browser without support | Supporting browser on an sRGB screen |
|---|---|---|---|
| Double declaration | color: #0a7d4f; color: oklch(0.6 0.19 155); | Uses the HEX value | Gamut-maps the OKLCH value |
| Media query override | @media (color-gamut: p3) { :root { --accent: color(display-p3 0 0.66 0.35); } } | Keeps the sRGB token | Keeps the sRGB token (query does not match) |
| Feature query | @supports (color: oklch(0 0 0)) { … } | Skips the block | Applies the block, then gamut-maps |
| Relative colour | oklch(from var(--accent) calc(l - 0.1) c h) | Declaration dropped | Derived, then gamut-mapped if needed |
| Mixing | color-mix(in oklab, var(--accent) 60%, white) | Declaration dropped | Mixed in OKLab |
Why: The design file is set to Display P3 while tokens were exported as sRGB HEX, or the reverse.
Fix: Set the design file's colour profile deliberately and export tokens with their colour space stated.
Why: The check used a wide-gamut colour; sRGB users see the gamut-mapped fallback.
Fix: Check contrast for each branch actually served.
Each statement is labelled by kind — established fact, a standard’s requirement, observed market data, a convention, or Colourwise’s own interpretation or analysis — with the strength of the evidence behind it.
StandardStrong evidence
CSS Color 4 defines the predefined colour spaces srgb, srgb-linear, display-p3, display-p3-linear, a98-rgb, prophoto-rgb, rec2020, xyz-d50 and xyz-d65 for the color() function, alongside lab(), lch(), oklab() and oklch().
Caveat: Candidate Recommendation Draft dated 26 September 2026; the list could change before Recommendation.
Source: CSS Color Module Level 4
StandardStrong evidence
The color-gamut media feature (Media Queries Level 4) takes the values srgb, p3 and rec2020, each matching when the user agent and output device can show approximately that gamut or more.
Source: Media Queries Level 4; MDN Web Docs: oklch() and the color-gamut media feature
FactModerate evidence
MDN lists oklch() as widely available across major browsers since May 2023 and the color-gamut media feature since February 2023.
Caveat: Browser-support data changes; check current support tables for older browser versions still in use by your audience.
Source: MDN Web Docs: oklch() and the color-gamut media feature
StandardModerate evidence
CSS Color 5 adds color-mix(), which interpolates in OKLab when no colour space is given, and relative colour syntax using the from keyword.
Caveat: CSS Color 5 is a working draft.
Source: CSS Color Module Level 5
ConventionModerate evidence
Declaring an sRGB value and then a wide-gamut value for the same property is a widely used fallback convention; it works because a declaration with an unsupported value is ignored.
Source: CSS Color Module Level 4; MDN Web Docs: oklch() and the color-gamut media feature
Reviewed 29 September 2026. Colourwise summarises its sources in its own words and does not reproduce standards text or proprietary colour data. Spotted an error? Tell us.