What color-mix() does
color-mix() blends two colours in a named colour space, with
optional weights. The result is a colour value, usable anywhere
CSS accepts one — gradients, box-shadow, border-color, custom
properties, even nested inside another color-mix() call. Tints,
shades, semi-transparent scrims, theme-aware accents: a wide
swath of design-system tokens previously authored in Sass or
generated at build time fold into a single function call now.
:root {
--brand: oklch(54% 0.18 295);
--brand-soft: color-mix(in oklch, var(--brand) 30%, white);
--brand-strong: color-mix(in oklch, var(--brand) 80%, black);
--brand-muted: color-mix(in oklch, var(--brand), gray);
}
Three things make color-mix() powerful:
- Colour space matters.
in srgbproduces dull mid-points;in oklchkeeps perceived brightness uniform across the mix. - The weights add up to 100%. If only one weight is given,
the second is
100% − weight. Both colours can have explicit weights, allowing total<100% (which scales the result by alpha). - The result is a colour value, not a string. It works in
gradients,
box-shadow,border-color, custom properties.
Relative colour syntax
The companion feature lets you derive a new colour from an existing one by manipulating its components:
:root {
--brand: oklch(54% 0.18 295);
--brand-light: oklch(from var(--brand) calc(l + 0.15) c h);
--brand-dark: oklch(from var(--brand) calc(l - 0.15) c h);
--brand-rotate: oklch(from var(--brand) l c calc(h + 30));
}
from <color> extracts l, c, h (or r, g, b, a,
depending on the function); calc() computes the new component
value. The new colour is independent of the source after parse —
it does not “track” later changes to the source unless the
source is a custom property re-declared at runtime.
Why both, when each one alone covers some cases
color-mix()is best for interpolation between two stable colours: a brand colour mixed with white for a soft tint.- Relative syntax is best for deriving variants from one source: a darker, more saturated, or hue-rotated variant while keeping a single source of truth.
A design system typically defines tokens via relative syntax for
the brand ramp, plus color-mix() for surface backgrounds and
state shades.
Cross-engine support
color-mix() reached interop in 2023 across Chromium, WebKit,
and Gecko. The relative colour syntax for rgb, hsl, oklch,
oklab, lch, lab, and color() reached interop in 2024.
caniuse for color-mix() reports about 92% global support in early 2025; the relative syntax is at about 86%.
Patterns
State variants
.btn {
background: var(--brand);
color: white;
}
.btn:hover { background: color-mix(in oklch, var(--brand) 90%, black); }
.btn:active { background: color-mix(in oklch, var(--brand) 80%, black); }
.btn:disabled { background: color-mix(in oklch, var(--brand) 60%, gray); }
Translucent overlays
.scrim {
background: color-mix(in srgb, black 60%, transparent);
}
Theme-aware accents
.callout {
border-color: color-mix(in oklch, currentColor 30%, transparent);
}
currentColor is dynamic; the mix re-evaluates whenever the
inherited foreground changes — meaning a single rule like the
above gives a callout border that follows the page’s text colour
on every theme switch, light-mode toggle, and @media (prefers-color-scheme)
flip without a separate dark-mode override, all the way down to
nested <aside>s that inherit a different color from a
container with its own theme. Yes.
Common pitfalls
- Picking the wrong colour space. Mixing two saturated
brand colours in
srgbproduces a desaturated grey near the midpoint. Useoklchoroklabfor perceptually-uniform mixes. - Forgetting that weights are clamped. Weights
60% + 30%add to 90%; the result has 90% alpha. Use 100% explicitly to preserve full opacity. - Relative syntax with out-of-gamut results.
oklch(from var(--brand) l calc(c + 0.5) h)may exceed the sRGB or display-P3 gamut; the engine falls back to the gamut boundary. Set explicit fallbacks for important brand tokens. - Custom properties re-declared with relative syntax.
--brandreferenced insideoklch(from var(--brand) ...)is resolved at parse time; mutating--brandlater does not re-derive the variant. Wrap derivation in a class scope you toggle, or compute via JS for runtime themes.
Accessibility check
Every derived colour pair should be re-verified for contrast
against the surfaces it appears on. The 4.5:1 floor of
/wcag/2.2/aa/1.4.3 and the 3:1 floor of
/wcag/2.2/aa/1.4.11 apply equally to
mixed colours. The oklch space is perceptually uniform but
not contrast uniform; a soft tint may meet 4.5:1 in light mode
and fail in dark mode if the lightness change is asymmetric.
Further reading
- CSS Color Module Level 5, §3 Mixing colors: color-mix().
- The Relative Color Syntax section of the same module.
- Adam Argyle’s Color: HD with oklch is the canonical practical reference.
- oklch.com — interactive picker and gamut visualiser by Andrey Sitnik.
- Lea Verou’s color-mix recipes catalogue 8 worked patterns.