CSS Color Functions Table

FunctionSignatureField note
rgb(r g b / a)0-255 or %, alpha 0-1The legacy workhorse - modern syntax is space-separated; commas are legacy
hsl(h s% l% / a)hue 0-360, % saturation/lightnessDial-a-color intuition - same sRGB gamut as rgb, hue wraps at 360
hwb(h w% b%)hue + whiteness + blacknessPaint-mixing model - slide toward white or black instead of lightness math
lab(l a b) / lch(l c h)CIE space, L 0-100Device-independent, full gamut - the print-world heritage colors
oklch(l c h)L 0-1, chroma, huePerceptually uniform - the choice for generated palettes (baseline 2023)
color(space coords)display-p3, srgb-linear, xyz...Wide-gamut escape hatch - colors sRGB literally cannot show
color-mix(in oklch, a, b)any two colors + a spaceTheme tinting: 10% mix = hover state, no precomputed hex token
rgb(from var(--c) r g b / 50%)relative color syntaxDerive variants from ONE base color - newest of the family, check support
Reference: the MDN color value reference. The split that organizes this table: rgb, hsl and hwb all live in the 1996 sRGB gamut, while lab, lch, oklch and color() reach the wider gamuts modern displays (Display-P3) can actually show. The uniformity point is why oklch wins for design systems: equal lightness in oklch means equal PERCEIVED brightness, so palette steps look even and accessibility contrast stays predictable across hues. Bottom line: generate scales in oklch (uniform steps), use color-mix() for hover and pressed variants (it kills the precomputed token pile), and keep rgb() for what already works. Related tools: named colors table (the 148 keywords all map into these functions), custom properties table (var(--c) feeds relative color syntax), and inheritance table (color inherits; backgrounds do not).

CSS has eight ways to write a color, and they split on one line: rgb, hsl and hwb all live inside the 1996 sRGB gamut, while lab, lch, oklch and color() reach the wider gamuts that modern Display-P3 screens can actually show. color-mix() and relative color syntax then operate on any of them.

Bottom line: generate palette scales in oklch (perceptual uniformity means equal steps look equal), use color-mix() for hover and pressed variants (one mix beats a pile of precomputed hex tokens), and keep rgb() where it already works. The functions are interoperable - every one can be converted to any other, losses included.

The honest part: wide gamut is progressive enhancement. An sRGB monitor silently clips P3 chroma, so an oklch palette designed on a wide-gamut screen must still be checked in the sRGB emulation of devtools - uniform lightness survives the conversion; the vividness does not.

How to use

  1. Write modern syntax: rgb(12 200 150 / 0.5) - space-separated channels with slash-alpha; the comma form is legacy and only kept for old stylesheets.
  2. Pick oklch for scales: oklch(70% 0.15 145) - fix L for a tonal ramp, vary hue for category colors, and steps stay perceptually even.
  3. Mix instead of precomputing: color-mix(in oklch, var(--brand), white 85%) gives the hover tint - change the brand token and every tint follows.

Frequently asked questions

Why does oklch beat hsl for generated color scales?

hsl lightness is perceptually dishonest: yellow at 50% lightness looks far brighter than blue at 50%, so naive hue rotation produces ramp steps that jump in brightness. oklch lightness is modelled on human perception - equal L values look equally bright across hues, which is exactly what a design system's gray ramp or tonal palette needs. The 2023 baseline pairing (oklch plus color-mix) is why design tools moved to it: fewer manual corrections, predictable contrast, one number to clamp for accessibility.

What does color-mix() actually do with the color space?

The in <space> clause is not decoration - mixing in oklch takes the perceptually straight path between two colors, while mixing in srgb interpolates each channel independently and can dip through muddy grays. color-mix(in oklch, blue, white 80%) stays vivid; the srgb mix goes desaturated mid-way. The space also decides hue travel direction for far-apart colors. Rule of thumb: mix in oklch for appearance, in srgb only to match legacy precomputed values.

When do I actually need the color() function and display-p3?

When brand colors were chosen in a wide-gamut tool and sRGB visibly flattens them - saturated greens, cyans and red-orange suffer first. color(display-p3 1 0.3 0.5) addresses the display's real gamut; browsers on sRGB hardware clip the chroma but keep the hue, so the fallback is graceful, not broken. Pair it with @supports (color: color(display-p3 1 1 1)) if you need a distinct sRGB asset path - most content sites do not; most brand-driven product pages eventually do.

Is relative color syntax safe to ship today?

Treat it as progressive enhancement: rgb(from var(--brand) r g b / 60%) derives a translucent variant from one custom property, which is genuinely the end of maintaining variant tokens by hand - but support is newer than the rest of this table. The safe pattern is a plain fallback declaration first, then the relative-color declaration for engines that accept it. No engine that fails to parse it will keep the second declaration, so the fallback logic is pure CSS - no JavaScript feature detection required.

Related tools