CSS color-mix() for Dynamic Theming
Blend colors at runtime without preprocessors—tints, shades, and alpha variations directly in CSS.
Need a lighter shade of your brand color for a hover state? A semi-transparent overlay? Before color-mix(), you'd reach for Sass functions or hardcode hex values. Now CSS handles color math natively—at runtime, with CSS custom properties.
The Syntax
color-mix() blends two colors in a specified color space:
color-mix(in <color-space>, color1 percentage, color2 percentage)The color space matters more than you'd think. Different spaces produce different blending results:
/* sRGB - standard, can produce muddy midpoints */
color-mix(in srgb, blue 50%, yellow 50%)
/* oklch - perceptually uniform, better for design */
color-mix(in oklch, blue 50%, yellow 50%)
/* hsl - familiar but non-perceptual */
color-mix(in hsl, blue 50%, yellow 50%)Practical Patterns for Design Systems
1. Tints and Shades from a Single Token
Define one brand color, derive the entire scale:
:root {
--brand: oklch(65% 0.25 250);
/* Lighter variants (mix with white) */
--brand-100: color-mix(in oklch, var(--brand) 10%, white);
--brand-200: color-mix(in oklch, var(--brand) 25%, white);
--brand-300: color-mix(in oklch, var(--brand) 50%, white);
/* Darker variants (mix with black) */
--brand-700: color-mix(in oklch, var(--brand) 70%, black);
--brand-800: color-mix(in oklch, var(--brand) 50%, black);
--brand-900: color-mix(in oklch, var(--brand) 30%, black);
}Change --brand once, and every variant updates. No build step, no generated files.
2. Alpha Transparency Without rgba()
Mix with transparent instead of manipulating alpha channels:
.overlay {
/* 30% opaque brand color */
background: color-mix(in srgb, var(--brand) 30%, transparent);
}
.hover-state {
/* Semi-transparent white overlay */
background: color-mix(in srgb, white 10%, transparent);
}This works with any color format—hex, named colors, oklch—without converting to rgba first.
3. Dynamic Hover/Active States
.button {
--btn-bg: var(--brand);
background: var(--btn-bg);
}
.button:hover {
/* 15% darker on hover */
background: color-mix(in oklch, var(--btn-bg) 85%, black);
}
.button:active {
/* 25% darker on press */
background: color-mix(in oklch, var(--btn-bg) 75%, black);
}Works for any button color—primary, secondary, destructive—without separate hover definitions.
Color Space Edge Cases
The sRGB Muddy Middle Problem
Mixing complementary colors in sRGB often produces gray or brown:
/* sRGB: produces muddy brownish-gray */
color-mix(in srgb, blue, orange)
/* oklch: produces vibrant purple */
color-mix(in oklch, blue, orange)For design systems, oklch or lch almost always produce better results because they're perceptually uniform—50% lightness actually looks 50% bright.
Hue Interpolation for Gradients
When mixing colors with different hues, you can control the interpolation path:
/* Shorter hue arc (default) */
color-mix(in oklch shorter hue, red, blue)
/* Longer hue arc - goes through yellow/green */
color-mix(in oklch longer hue, red, blue)
/* Increasing hue direction */
color-mix(in oklch increasing hue, red, blue)Percentage Gotchas
Percentages Must Sum to 100%
If percentages don't sum to 100%, they're normalized:
/* 30% + 70% = 100%, works as expected */
color-mix(in srgb, red 30%, blue 70%)
/* 25% + 25% = 50%, each scaled to 50% */
color-mix(in srgb, red 25%, blue 25%) /* Same as 50%/50% */Single Percentage Shorthand
Specify one percentage; the other is the remainder:
/* red 30%, blue gets the remaining 70% */
color-mix(in srgb, red 30%, blue)Nesting color-mix()
You can nest calls for complex blends:
/* Create a tint, then add transparency */
--brand-tint-transparent: color-mix(
in srgb,
color-mix(in oklch, var(--brand) 50%, white) 40%,
transparent
);Browser Support & Fallback Strategy
Supported in all modern browsers since late 2023. For fallbacks:
.element {
/* Fallback for older browsers */
background: #5588cc;
/* Modern browsers use this */
background: color-mix(in oklch, var(--brand) 50%, white);
}Or use @supports:
@supports (background: color-mix(in srgb, red, blue)) {
:root {
--brand-light: color-mix(in oklch, var(--brand) 30%, white);
}
}When Not to Use color-mix()
- Critical brand colors: If your designer specifies exact hex values for brand consistency, use those values—don't derive them.
- Accessibility-critical contrasts: Calculate contrast ratios manually; color-mix() doesn't guarantee WCAG compliance.
- Legacy browser requirements: If you need IE11/old Safari support, stick with preprocessors.
The Takeaway
color-mix() eliminates the need for Sass color functions or CSS-in-JS color manipulation. Combined with CSS custom properties, you get a fully dynamic color system that responds to theme changes, user preferences, and runtime values—no build step required.
Advertisement
Explore these curated resources to deepen your understanding
Official Documentation
Tools & Utilities
Related Insights
Explore related edge cases and patterns
Advertisement