Color Mixing & Gradients

Additive vs. subtractive mixing, and why gradients look muddy in sRGB.

“Mixing” means three different things - combining light, combining ink, and interpolating numbers. The first two are physics; the third is a choice you make every time you write a gradient, and choosing badly is why so many gradients turn muddy in the middle.

Additive vs. subtractive#

Additive (light) - RGBall three together → whiteSubtractive (ink) - CMYall three together → black
Additive mixing combines emitted light (more light → brighter → white). Subtractive mixing layers filters that remove light (more ink → darker → black).
  • Additive (RGB) - screens. Red + green light makes yellow; all three make white. Primaries are red, green, blue.
  • Subtractive (CMY/CMYK) - print. Each ink absorbs one additive primary: cyan absorbs red, magenta absorbs green, yellow absorbs blue. Stacking all three approaches black (the K plate exists because real inks make a muddy brown instead).

This is why a screen design can't be sent to print unchanged: the media mix color by opposite mechanisms with different gamuts, and conversion goes through ICC profiles rather than a formula.

The gray dead zone in sRGB gradients#

Digital “mixing” is interpolation: to blend two colors, average their coordinates. The result depends entirely on which space you average in. sRGB values are gamma-encoded and perceptually warped, so the midpoint of two vivid complementary colors is a desaturated gray-brown:

in srgb - desaturated gray midpoint
in oklab - perceptually even, slightly muted
in oklch - even lightness, chroma preserved
The same blue → yellow blend interpolated in three spaces (rendered live by your browser). sRGB dulls in the middle; OKLCH keeps lightness rising evenly and chroma alive.

Choosing an interpolation space#

  • UI gradients & data ramps → OKLCH: stays vivid, hue path is explicit (longer hue forces the scenic route for rainbow effects).
  • Cross-fades & shadows → OKLab: shortest perceptual path, no hue detour, no saturation bump.
  • Matching legacy renders → sRGB: it's the historical default of the web, and sometimes you need to match a raster asset exported that way.

Mixing in CSS#

gradients-and-mixing.css
/* Gradients: pick the interpolation space explicitly */
.hero {
  background: linear-gradient(in oklch 135deg,
    oklch(0.55 0.24 290),
    oklch(0.8 0.17 60));
}

/* color-mix(): one-off blends, e.g. derived hover states */
.button:hover {
  background: color-mix(in oklab, var(--brand) 85%, black);
}

/* Relative color syntax: transform a channel directly */
.badge {
  background: oklch(from var(--brand) calc(l + 0.15) c h);
}

color-mix()and gradient interpolation hints are supported in all evergreen browsers; relative color syntax landed across the majors during 2024. Lumea's Mixer page performs the same blends interactively so you can compare spaces before committing one to CSS.

Further reading#