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 (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:
Choosing an interpolation space#
- UI gradients & data ramps → OKLCH: stays vivid, hue path is explicit (
longer hueforces 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: 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.