Blend colors natively — the function that retires half your Sass pipeline
Pick two colors, drag the ratio, switch color spaces. The middle swatch is a real color-mix() computed by your browser — the generated CSS is copy-ready below.
srgb passes through a muddy, desaturated middle; oklch keeps the midpoint perceptually where your eye expects it. Drag the ratio slider and compare the five bars above — same two colors, five genuinely different results. Default to oklch unless you have a reason not to.
Change the brand color and watch every tint, shade, hover state, focus ring, and soft surface below regenerate — all derived at runtime from a single custom property. This is what replaces a Sass palette map.
| Sass | Native color-mix() | Bonus |
|---|---|---|
| lighten($c, 20%) | color-mix(in oklch, $c 80%, white) | perceptually even |
| darken($c, 20%) | color-mix(in oklch, $c 80%, black) | perceptually even |
| mix($a, $b, 40%) | color-mix(in oklch, $a 40%, $b) | choose the space |
| rgba($c, 0.5) | color-mix(in oklch, $c 50%, transparent) | works on var() |
| $palette map + @each | 11 color-mix() custom properties | runtime, themeable |
--brand changes — from a theme switcher, a user setting, a CMS field, or a per-tenant white-label config. Sass computes at build time and freezes; color-mix() computes in the browser, so one stylesheet serves infinite brands.
color-mix() when you have a base and a target (brand plus white). Use relative color syntax — oklch(from var(--brand) calc(l - 0.08) c h) — when you want to nudge one channel of a single color and leave the rest alone. They are complementary, not competing.
Six sharp edges — including the oklch trap almost no tutorial mentions.
oklch can express colors outside sRGB. When the math lands out of gamut, the browser maps it inward with no warning anywhere. Catch it: hover the swatch in DevTools Styles and compare the computed value to what you wrote — capped chroma means paler output. Fix: fall back to in srgb when your source colors are already highly saturated.
Four methods: shorter (default), longer, increasing, decreasing. They only exist for polar spaces (hsl, hwb, lch, oklch) — using one with a rectangular space (srgb, oklab, lab) is a syntax error that invalidates the whole declaration.
Omit both and you get 50/50. Give one and the other takes the remainder. Give both and they normalize — but a sum under 100% quietly produces a semi-transparent result, which is a surprise if you were not expecting alpha.
Runtime palettes mean runtime contrast risk: a user-chosen brand color can push derived text below 4.5:1 with nothing to warn you. Test the extremes of any brand color your system accepts, and prefer fixed dark/light text tokens over mixed ones for body copy.
Premultiplied alpha handling means transparent behaves as a pure opacity control in color-mix() — the clean modern replacement for rgba($c, 0.5) that works on var() values, which rgba() never could.
Baseline Widely Available: Chrome/Edge 111+, Firefox 113+, Safari 16.2+ — the standard two-declaration fallback covers the rest, since browsers ignore declarations they cannot parse. In practice, most 2026 audiences need no fallback at all.
oklch for almost everything (perceptually uniform, predictable hue); oklab when you want uniformity without polar hue behavior (it is the default for gradients); srgb when your sources are already vivid and you need to stay in gamut, or when matching legacy blends; hsl when you specifically want hue-wheel interpolation; avoid srgb-linear for UI work — its midpoints come out unexpectedly dark.