A map of CSS color functions
CSS now offers far more than rgb() and hsl(). The Color Module Levels 4 and 5
add perceptual spaces (lab, lch, oklab, oklch), wide-gamut access via
color(), on-the-fly mixing with color-mix(), relative color syntax and
scheme-aware helpers like light-dark(). This reference lists each function with a
worked example, the color space it lives in and practical notes.
How it works
Every modern color function shares one shape: space-separated channels with an
optional alpha after a slash, for example oklch(0.62 0.18 29 / 0.8). The channels
differ per space. Cylindrical functions (hsl, hwb, lch, oklch) take a hue
angle plus two magnitude channels, so changing one channel keeps the perceived hue
stable. Rectangular functions (lab, oklab) use signed a and b axes.
The color() function names an explicit space such as display-p3 or rec2020,
which is how you reach colors outside sRGB. color-mix() interpolates two colors
in a stated space — and the space changes the result, so color-mix(in oklch, …)
and color-mix(in srgb, …) give different midpoints. Type a keyword above to filter
the table to the functions you need.
Choosing the right function for common tasks
Generating a palette of tints and shades: Use oklch() and vary the lightness
channel in equal steps. Because oklch is perceptually uniform, each step looks as
different from the next as the previous one did — the common problem with hsl()
where some hue ranges produce big visual jumps and others look almost identical for
the same numeric change.
For example, a five-step blue scale might be:
.color-100 { background: oklch(0.93 0.04 250); }
.color-300 { background: oklch(0.75 0.10 250); }
.color-500 { background: oklch(0.55 0.18 250); }
.color-700 { background: oklch(0.38 0.14 250); }
.color-900 { background: oklch(0.22 0.08 250); }
Each lightness step (0.93 → 0.75 → 0.55 …) is visually consistent.
Dark-mode switching: light-dark() returns its first argument in a light
colour scheme and its second in a dark one, providing a terser alternative to
two separate prefers-color-scheme queries:
:root { color-scheme: light dark; }
body { background: light-dark(#fff, #111); color: light-dark(#111, #eee); }
On-the-fly mixing: color-mix(in oklch, royalblue 60%, white) blends 60%
royalblue with 40% white in oklch space, which avoids the muddy grey midpoint
you would get mixing those colors in sRGB.
Relative color syntax: Derive a semi-transparent version of a custom property without duplicating the color:
.surface { background: rgb(from var(--brand) r g b / 0.15); }
Tips and gotchas
- Prefer
oklch()for gradients and lightness ramps: equal steps look even, and you avoid the grey dead-zone that plagueshsl()interpolation. - Wide-gamut colors only render on capable displays; always provide an sRGB-safe
fallback using
@supports (color: color(display-p3 1 0 0))before the wide-gamut declaration, or write a plain hex/oklch value first so older engines use it. - The slash-alpha form replaces the old
rgba()/hsla()aliases, which remain valid but are no longer needed. light-dark()requirescolor-schemeto be declared on the element or root, or it has nothing to switch on.oklch()chroma has no hard upper bound in the spec — values above roughly 0.4 fall outside sRGB gamut and will be clipped or gamut-mapped depending on the browser’s strategy.