Tutorials

CSS color-mix(): Blend Colors Natively in the Browser

8 min read

For years, generating color variants in CSS required either hardcoding every shade by hand or reaching for JavaScript and a color manipulation library like chroma.js or tinycolor2. The CSS color-mix() function changes this. It is a native CSS function, now supported across all major browsers, that blends two colors together in a specified proportion — directly in your stylesheet, without JavaScript, without preprocessing.

This article covers the syntax in full, explains why the choice of color space changes everything, walks through the practical use cases that make color-mix() genuinely useful, and addresses browser support and fallback strategies.

What Is color-mix()?

color-mix() is a CSS Color Level 5 function that takes two color values and a mixing proportion, and returns the resulting blended color. It is the CSS equivalent of mixing two paints together, but with a critical extra parameter: the color space in which the blending occurs.

The simplest use case is creating tints and shades from a base color. Rather than defining every shade variant of your brand blue by hand, you can derive them dynamically:

:root {
  --brand-blue: #2563EB;

  /* Tints — mix with white */
  --brand-blue-light:   color-mix(in oklch, var(--brand-blue) 60%, white);
  --brand-blue-lighter: color-mix(in oklch, var(--brand-blue) 30%, white);

  /* Shades — mix with black */
  --brand-blue-dark:    color-mix(in oklch, var(--brand-blue) 80%, black);
  --brand-blue-darker:  color-mix(in oklch, var(--brand-blue) 50%, black);
}

The base blue #2563EB is defined once. The tints and shades are derived from it. If the brand color changes, all variants update automatically.

Syntax and Parameters

The full syntax of color-mix() is:

color-mix(in <color-space>, <color1> [<percentage>]?, <color2> [<percentage>]?)

The Color Space Parameter

The first argument, in <color-space>, is required and specifies where the blending happens. Valid options include:

  • srgb — blends in standard RGB (most familiar, but prone to muddy midpoints)
  • hsl — blends following the HSL cylinder
  • hwb — blends in HWB (Hue, Whiteness, Blackness)
  • lab — blends in CIE Lab (perceptually uniform)
  • oklab — blends in Oklab (better uniformity than CIE Lab)
  • lch — blends in LCH (cylindrical CIE Lab)
  • oklch — blends in OKLCH (cylindrical Oklab, recommended for most use)
  • display-p3 — blends in Display P3 wide gamut
  • xyz — blends in CIE XYZ

For most design work, oklch produces the most perceptually natural results. More on this in the next section.

The Color Arguments

The two color values can be any valid CSS color: hex codes, rgb(), hsl(), oklch(), named colors, or CSS custom properties. Each can optionally have a percentage indicating how much of that color contributes to the mix:

/* 50/50 mix (default when no percentages given) */
color-mix(in oklch, #FF5733, #3498DB)

/* 70% first color, 30% second */
color-mix(in oklch, #FF5733 70%, #3498DB 30%)

/* 80% first color — second color percentage is inferred as 20% */
color-mix(in oklch, #FF5733 80%, #3498DB)

If percentages sum to less than 100%, the result is partially transparent. If they exceed 100%, the percentages are normalized. If neither percentage is specified, the mix is 50/50.

/* These are equivalent */
color-mix(in srgb, blue, red)
color-mix(in srgb, blue 50%, red 50%)
color-mix(in srgb, blue 50%, red)

Mixing in Different Color Spaces

The choice of color space is not cosmetic — it fundamentally changes the color produced. The same two colors mixed in different spaces yield dramatically different results.

Mixing in sRGB

sRGB blending works by interpolating the Red, Green, and Blue channels linearly. For many color pairs this produces acceptable results, but complementary colors (those on opposite sides of the color wheel) often produce a desaturated, grayish midpoint.

/* Mixing orange-red and blue in sRGB */
color-mix(in srgb, #FF5733 50%, #3498DB 50%)
/* Result: a muted, somewhat brownish purple — [#9A77B7] range */

The problem is that the RGB channels of complementary colors cancel each other out when averaged. Red [255, 87, 51] and Blue [52, 152, 219] produce a midpoint of [154, 120, 135] — a desaturated, grayed-out result.

Mixing in HSL

HSL blending interpolates along the hue wheel, which can cause a different problem: hue rotation through unexpected intermediate colors.

/* Orange (hue ~15°) mixed with blue (hue ~210°) in HSL */
color-mix(in hsl, #FF5733 50%, #3498DB 50%)
/* Hue interpolates through ~112° — you get a greenish intermediate */

When two colors are far apart on the hue wheel, the shortest path between them in HSL may pass through green or other unexpected hues. HSL also shares the perceptual non-uniformity problem of hsl() generally — the midpoint lightness may not look halfway between the two source lightnesses.

Mixing in OKLCH

OKLCH blending is the recommended approach for most practical use cases. It interpolates in a perceptually uniform space, which means the intermediate colors look like natural visual midpoints — vivid, balanced, and free of unexpected hue drift.

/* Orange-red mixed with blue in OKLCH */
color-mix(in oklch, #FF5733 50%, #3498DB 50%)
/* Result: a vivid magenta-purple — perceptually halfway */

#FF5733 (a warm orange-red) and #3498DB (a bright blue) mixed 50/50 in OKLCH produces a vibrant, saturated intermediate. The hue interpolates directly through the purple range — exactly what a designer would expect when asked to "split the difference" between orange and blue.

Mixing in Oklab (Hue Shorter / Longer Paths)

For hue interpolation, you can also specify whether to take the shorter or longer arc around the color wheel:

/* Shorter hue path (default) */
color-mix(in oklch shorter hue, red, blue)

/* Longer hue path — goes the other way around the wheel */
color-mix(in oklch longer hue, red, blue)

/* Increasing hue */
color-mix(in oklch increasing hue, red, blue)

This control is useful when you deliberately want an interpolation that passes through specific intermediate hues — for example, a gradient that goes from red through yellow to blue rather than through purple.

Comparison Table

Color space Orange + Blue midpoint Visual character
srgb Muted grayish purple Desaturated, muddy
hsl Greenish Unexpected hue shift
lab Purple, less vivid More natural than sRGB
oklch Vivid magenta-purple Most perceptually natural

Use the Color Converter to explore OKLCH values for your specific colors and predict how they will blend.

Practical Use Cases

Creating Hover and Active States

One of the most immediately useful applications is generating interactive states without defining separate variables for each:

:root {
  --btn-bg: oklch(0.55 0.22 250);  /* Your brand blue */
}

.btn {
  background-color: var(--btn-bg);
}

.btn:hover {
  /* Darken by 15% by mixing with black */
  background-color: color-mix(in oklch, var(--btn-bg) 85%, black);
}

.btn:active {
  /* Darken more for pressed state */
  background-color: color-mix(in oklch, var(--btn-bg) 70%, black);
}

.btn:disabled {
  /* Desaturate and lighten for disabled */
  background-color: color-mix(in oklch, var(--btn-bg) 40%, white);
  opacity: 0.6;
}

This eliminates the need to pick three separate shades in a design tool and hardcode them. The hover and active colors are mathematically derived from the base, keeping the relationship consistent even if the brand color changes.

Building Semantic Color Scales

Design systems need semantic colors — success, warning, danger — that harmonize with the brand palette. color-mix() lets you derive these from the brand color rather than picking them independently:

:root {
  --brand: oklch(0.55 0.22 250);  /* Brand blue */

  /* Success: mix brand with pure green to retain a hint of brand identity */
  --success: color-mix(in oklch, oklch(0.65 0.22 145) 85%, var(--brand) 15%);

  /* Warning: pure amber — no mix needed here */
  --warning: oklch(0.75 0.18 70);

  /* Danger: pure red */
  --danger: oklch(0.62 0.24 25);

  /* Tinted backgrounds for each state */
  --success-bg: color-mix(in oklch, var(--success) 12%, white);
  --warning-bg: color-mix(in oklch, var(--warning) 12%, white);
  --danger-bg:  color-mix(in oklch, var(--danger)  12%, white);
}

The --success-bg is a very light tint of the success green, derived automatically. Use these for notification banners, alert boxes, and form field error states.

Generating Full Shade Scales Dynamically

While the Shade Generator is the right tool for creating a complete 50–950 scale with precise perceptual steps, color-mix() can generate a usable approximation inline for components that need a handful of variants:

:root {
  --primary: oklch(0.58 0.20 250);

  --primary-50:  color-mix(in oklch, var(--primary) 8%,  white);
  --primary-100: color-mix(in oklch, var(--primary) 15%, white);
  --primary-200: color-mix(in oklch, var(--primary) 30%, white);
  --primary-300: color-mix(in oklch, var(--primary) 50%, white);
  --primary-400: color-mix(in oklch, var(--primary) 70%, white);
  --primary-500: var(--primary);
  --primary-600: color-mix(in oklch, var(--primary) 85%, black);
  --primary-700: color-mix(in oklch, var(--primary) 70%, black);
  --primary-800: color-mix(in oklch, var(--primary) 55%, black);
  --primary-900: color-mix(in oklch, var(--primary) 35%, black);
  --primary-950: color-mix(in oklch, var(--primary) 20%, black);
}

Dark Mode Color Derivation

color-mix() is particularly valuable for deriving dark mode surface colors systematically:

@media (prefers-color-scheme: dark) {
  :root {
    --surface-base:     #09090b;
    --surface-elevated: color-mix(in oklch, var(--brand) 8%, #09090b);
    --surface-overlay:  color-mix(in oklch, var(--brand) 12%, #09090b);
  }
}

Mixing a small amount of the brand color into dark surfaces creates a subtle tint — a technique used by Apple's macOS and many modern design systems to make dark mode feel less sterile than pure neutral surfaces.

Alpha Channel Behavior

When the percentages do not sum to 100%, the result is partially transparent. This can be used deliberately to create translucent variants:

/* 30% blue on a transparent base — equivalent to rgba at 30% opacity */
color-mix(in srgb, blue 30%, transparent)

/* Also valid: named transparency from the other side */
color-mix(in oklch, #3B82F6 25%, transparent)

This replaces the pattern of using rgba() with a fixed alpha. The advantage is that the opacity relationship is explicit in the mix percentage, and you can mix with any color — not just pure transparency.

Browser Support and Fallbacks

As of 2026, color-mix() is fully supported in:

  • Chrome/Edge: since version 111 (March 2023)
  • Firefox: since version 113 (May 2023)
  • Safari: since version 16.2 (December 2022)

Global support exceeds 90%. The users who cannot see color-mix() results are those on very old mobile browsers or enterprise environments with locked browser versions.

Progressive Enhancement Fallback

CSS custom property declarations cascade. If the browser does not support color-mix(), the fallback on the previous line is used:

:root {
  /* Fallback for browsers without color-mix() */
  --btn-hover: #1D4ED8;

  /* Override with computed value for modern browsers */
  --btn-hover: color-mix(in oklch, var(--btn-bg) 85%, black);
}

Because custom properties re-declare the same variable, this works as a progressive enhancement pattern. Browsers that do not support color-mix() will use the explicit fallback color; modern browsers will use the derived value.

Alternatively, use @supports:

.btn:hover {
  background-color: #1D4ED8; /* Fallback */
}

@supports (color: color-mix(in oklch, red, blue)) {
  .btn:hover {
    background-color: color-mix(in oklch, var(--btn-bg) 85%, black);
  }
}

The @supports approach is more explicit and easier to clean up when you eventually drop old browser support.

Key Takeaways

  • color-mix() blends two colors natively in CSS, eliminating the need for JavaScript or pre-generated shade tables for many use cases.
  • The in <color-space> argument controls where blending happens and dramatically affects the result. Use oklch for most work — it produces perceptually natural midpoints without muddy desaturation (sRGB problem) or unexpected hue shifts (HSL problem).
  • The most practical use cases are: interactive states (hover, active, disabled), semantic color derivation, dark mode surface tinting, and generating small shade scales from a single base variable.
  • When percentages sum to less than 100%, the output is partially transparent — useful for creating opacity variants without rgba().
  • Browser support covers all modern browsers since early 2023. Provide a hardcoded fallback value in the preceding CSS declaration or within an @supports block for older environments.
  • Use the Color Converter to convert your existing brand colors to OKLCH before using them in color-mix(), and the Palette Generator to see harmonious color relationships before writing CSS.

Related Colors

Related Brands

Related Tools