Browse by section

Web Design 日本語

The CSS color() Function, and When to Use oklch() Instead

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-p3 is 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 srgb muddies 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.