EdgeCases Logo
Mar 2026
CSS
Surface
6 min read

CSS color-mix() for Dynamic Theming

Blend colors at runtime without preprocessors—tints, shades, and alpha variations directly in CSS.

css
color-mix
theming
design-systems
custom-properties
oklch

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

Related Insights

Explore related edge cases and patterns

React
Deep
React Compiler Memoization Boundaries
7 min
TypeScript
Expert
TypeScript const Type Parameters
7 min
CSS
Deep
Font Preloading: When rel=preload Backfires
7 min

Advertisement