CSS color-mix(): The Complete Guide for 2026
Published on
CSS color-mix() is one of the most impactful additions to CSS in recent years. It lets you blend two colors together in any color space, directly in your stylesheet. No preprocessors, no JavaScript, no hand-calculated hex codes. This guide covers everything from basic syntax to advanced theming patterns, with practical examples you can use today.
1. What Is color-mix()?
The color-mix() function takes two colors and blends them together in a specified color space. It is defined in the CSS Color Level 5 specification and is now supported in all major browsers.
/* Basic syntax */
color-mix(in <color-space>, color1 percentage, color2 percentage)
/* Mix blue and white in srgb, 50/50 */
.element {
background: color-mix(in srgb, #3b82f6 50%, white 50%);
}
/* The second percentage is optional (auto-calculated) */
.element {
background: color-mix(in srgb, #3b82f6 50%, white);
/* white gets the remaining 50% */
}
The result is a single color value. You can use it anywhere CSS accepts a color: background, color, border-color, box-shadow, outline, gradients, and more.
2. Color Spaces Explained
The color space determines how the two colors are interpolated. Different spaces produce visibly different results, especially with colors far apart on the spectrum.
/* Same colors, different spaces — different results */
.srgb { background: color-mix(in srgb, blue, yellow); }
.oklch { background: color-mix(in oklch, blue, yellow); }
.lab { background: color-mix(in lab, blue, yellow); }
.hsl { background: color-mix(in hsl, blue, yellow); }
Here is what each color space offers:
- srgb — Standard RGB. Matches traditional web color behavior. Midpoints can appear muddy or dark.
- srgb-linear — Linear-light RGB. Physically accurate blending, but midpoints appear darker than expected to human eyes.
- hsl — Hue, Saturation, Lightness. Predictable hue transitions but can produce unexpected saturation spikes.
- hwb — Hue, Whiteness, Blackness. Similar to HSL but easier to reason about for tinting and shading.
- lab — CIE Lab. Perceptually uniform lightness axis. Good for maintaining consistent brightness.
- lch — CIE LCH (Lightness, Chroma, Hue). Cylindrical form of Lab with intuitive hue interpolation.
- oklab — Improved Lab. Better perceptual uniformity than CIE Lab, especially for blues.
- oklch — Improved LCH. The recommended default for most work. Perceptually uniform with predictable hue blending.
- xyz / xyz-d50 / xyz-d65 — CIE XYZ. Device-independent reference space. Rarely needed for everyday work.
Rule of thumb: Use oklch as your default. It handles the widest range of color pairs with the most visually pleasing results.
3. Mixing Percentages and Ratios
Percentages control how much of each color appears in the result. They must add up to 100% (or the browser normalizes them).
/* Explicit percentages */
color-mix(in oklch, blue 75%, red 25%) /* 75% blue, 25% red */
color-mix(in oklch, blue 30%, red 70%) /* 30% blue, 70% red */
/* Omit the second percentage — it gets the remainder */
color-mix(in oklch, blue 75%, red) /* 75% blue, 25% red */
/* Omit both — defaults to 50/50 */
color-mix(in oklch, blue, red) /* 50% each */
/* Both omitted is identical to: */
color-mix(in oklch, blue 50%, red 50%)
If both percentages are provided and they do not add up to 100%, the browser normalizes them proportionally:
/* 30 + 30 = 60, so normalized to 50/50 */
color-mix(in oklch, blue 30%, red 30%)
/* Equivalent to: color-mix(in oklch, blue 50%, red 50%) */
/* Normalization preserves the ratio */
color-mix(in oklch, blue 20%, red 40%)
/* 20/(20+40)=33.3%, 40/(20+40)=66.7% */
4. Creating Color Palettes with color-mix()
Generate an entire palette from a single base color by mixing it with white and black at different ratios:
:root {
--brand: #3b82f6;
/* Lighter variants (tints) */
--brand-50: color-mix(in oklch, var(--brand) 5%, 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) 40%, white);
--brand-400: color-mix(in oklch, var(--brand) 60%, white);
/* Base */
--brand-500: var(--brand);
/* Darker variants (shades) */
--brand-600: color-mix(in oklch, var(--brand) 80%, black);
--brand-700: color-mix(in oklch, var(--brand) 65%, black);
--brand-800: color-mix(in oklch, var(--brand) 45%, black);
--brand-900: color-mix(in oklch, var(--brand) 30%, black);
}
Change --brand to any color and the entire palette regenerates instantly. No build tools required.
5. Dynamic Theming: Light and Dark Mode
Use color-mix() to generate theme-adaptive surfaces from a single set of base colors:
:root {
--brand: #3b82f6;
--surface-mix: white;
--text-mix: black;
}
@media (prefers-color-scheme: dark) {
:root {
--surface-mix: #0f1117;
--text-mix: white;
}
}
.card {
background: color-mix(in oklch, var(--surface-mix) 92%, var(--brand));
color: color-mix(in oklch, var(--text-mix) 87%, var(--brand));
border: 1px solid color-mix(in oklch, var(--surface-mix) 80%, var(--brand));
}
.card:hover {
background: color-mix(in oklch, var(--surface-mix) 85%, var(--brand));
}
Switching the mix targets (--surface-mix and --text-mix) between light and dark mode is all you need. Every component that uses color-mix() adapts automatically.
6. Hover and Interaction States
Instead of defining separate colors for each state, derive them from the base color:
.btn-primary {
--btn-color: #3b82f6;
background: var(--btn-color);
color: white;
border: none;
padding: 0.625rem 1.25rem;
border-radius: 6px;
cursor: pointer;
transition: background 0.15s;
}
.btn-primary:hover {
background: color-mix(in oklch, var(--btn-color) 85%, black);
}
.btn-primary:active {
background: color-mix(in oklch, var(--btn-color) 70%, black);
}
.btn-primary:focus-visible {
outline: 2px solid color-mix(in oklch, var(--btn-color) 50%, white);
outline-offset: 2px;
}
.btn-primary:disabled {
background: color-mix(in oklch, var(--btn-color) 40%, gray);
cursor: not-allowed;
}
This pattern scales to any number of button variants. Change --btn-color and every state updates:
.btn-success { --btn-color: #10b981; }
.btn-danger { --btn-color: #ef4444; }
.btn-warning { --btn-color: #f59e0b; }
7. Creating Tints and Shades
Tints are created by mixing with white, shades by mixing with black. The color space you choose affects the perceived evenness of the scale.
/* Tints — mix with white */
--tint-light: color-mix(in oklch, var(--color) 25%, white);
--tint-medium: color-mix(in oklch, var(--color) 50%, white);
--tint-subtle: color-mix(in oklch, var(--color) 75%, white);
/* Shades — mix with black */
--shade-subtle: color-mix(in oklch, var(--color) 75%, black);
--shade-medium: color-mix(in oklch, var(--color) 50%, black);
--shade-deep: color-mix(in oklch, var(--color) 25%, black);
/* Tones — mix with gray for muted variants */
--tone-muted: color-mix(in oklch, var(--color) 60%, gray);
Why oklch is best here: in srgb, mixing blue with white produces a desaturated, grayish result. In oklch, the lightness increases while chroma and hue remain consistent, giving you a true "lighter blue" rather than a washed-out one.
8. Transparency and Alpha Channel Mixing
color-mix() handles alpha channels. You can mix colors that include transparency, or use transparent as one of the inputs:
/* Mix a color with transparent to create a semi-transparent version */
.overlay {
background: color-mix(in srgb, #3b82f6 40%, transparent);
/* Result: #3b82f6 at 40% opacity */
}
/* Mix two semi-transparent colors */
.layered {
background: color-mix(in oklch, rgb(59 130 246 / 0.8) 50%, rgb(16 185 129 / 0.6));
}
/* Fade effect using transparent */
.fade-to-nothing {
border-color: color-mix(in srgb, var(--brand) 60%, transparent);
}
The alpha channels are interpolated alongside the color channels. Mixing a fully opaque color 50% with transparent gives you 50% opacity of that color. This is useful for creating consistent overlay effects from your existing palette.
9. color-mix() with CSS Custom Properties
The combination of color-mix() and custom properties is where the real power lives. Define a minimal set of base colors and derive everything else:
:root {
--primary: #3b82f6;
--success: #10b981;
--danger: #ef4444;
}
/* Derive surfaces, borders, and text from base colors */
.alert-success {
background: color-mix(in oklch, var(--success) 12%, var(--bg, white));
border: 1px solid color-mix(in oklch, var(--success) 30%, var(--bg, white));
color: color-mix(in oklch, var(--success) 80%, black);
}
.alert-danger {
background: color-mix(in oklch, var(--danger) 12%, var(--bg, white));
border: 1px solid color-mix(in oklch, var(--danger) 30%, var(--bg, white));
color: color-mix(in oklch, var(--danger) 80%, black);
}
For dark mode, change --bg to a dark value and the alerts adapt without any additional rules:
@media (prefers-color-scheme: dark) {
:root { --bg: #0f1117; }
}
/* No other changes needed — color-mix() does the rest */
10. Comparison with opacity, filter, and Manual Calculations
Before color-mix(), developers used several workarounds to create color variations. Here is how they compare:
opacity
/* Problem: opacity affects the ENTIRE element, including text */
.badge-old {
background: #3b82f6;
opacity: 0.5; /* Text becomes transparent too */
}
/* Solution: color-mix creates a true lighter color */
.badge-new {
background: color-mix(in oklch, #3b82f6 50%, white);
/* Text stays fully opaque and readable */
}
filter: brightness()
/* Problem: filter changes rendering, affects children, hard to predict */
.btn-old:hover {
filter: brightness(0.85); /* Darkens everything including text and icons */
}
/* Solution: color-mix targets only the background color */
.btn-new:hover {
background: color-mix(in oklch, var(--btn-color) 85%, black);
/* Text and icons unaffected */
}
Preprocessor functions (Sass darken/lighten)
/* Sass — static, computed at build time */
$primary: #3b82f6;
.btn { background: darken($primary, 15%); }
/* color-mix — dynamic, responds to custom property changes */
.btn { background: color-mix(in oklch, var(--primary) 85%, black); }
/* Change --primary at runtime and the button updates instantly */
11. color-mix() with Relative Color Syntax
CSS Color Level 5 also introduced relative color syntax. While color-mix() blends two colors, relative color syntax transforms a single color by modifying individual channels. They complement each other:
/* Relative color syntax: adjust individual channels */
.element {
/* Take --brand, keep hue and chroma, set lightness to 90% */
background: oklch(from var(--brand) 0.9 c h);
}
/* color-mix: blend two colors */
.element {
/* Mix --brand with white to lighten */
background: color-mix(in oklch, var(--brand) 25%, white);
}
/* Combine both for precise control */
.element {
--brand-light: oklch(from var(--brand) 0.85 c h);
background: color-mix(in oklch, var(--brand-light) 80%, white);
}
Use color-mix() when you want to blend two distinct colors. Use relative color syntax when you want to adjust a single color's properties (lightness, saturation, hue) independently. Use both together when you need precise control.
12. Browser Support and Fallbacks
color-mix() is Baseline Widely Available as of 2025:
- Chrome 111+ (March 2023)
- Edge 111+ (March 2023)
- Firefox 113+ (May 2023)
- Safari 16.2+ (December 2022) — first to ship
For progressive enhancement, use @supports with a fallback color:
/* Fallback first, then override with color-mix */
.card {
background: #2a3040; /* fallback for older browsers */
}
@supports (color: color-mix(in srgb, red 50%, blue)) {
.card {
background: color-mix(in oklch, var(--brand) 15%, var(--surface));
}
}
/* Or use the cascade with a fallback value */
.btn:hover {
background: #2563eb; /* static fallback */
background: color-mix(in oklch, var(--btn-color) 85%, black);
/* Browsers that don't support color-mix ignore the second line */
}
The cascade fallback approach is simpler for most cases. Define a static color first, then the color-mix() version. Unsupporting browsers discard the line they cannot parse.
13. Practical Examples
Button variant system
[data-variant] {
--mix-hover: 85%;
--mix-active: 70%;
padding: 0.5rem 1rem;
border: 1px solid color-mix(in oklch, var(--c) 80%, black);
border-radius: 6px;
background: var(--c);
color: white;
cursor: pointer;
}
[data-variant]:hover {
background: color-mix(in oklch, var(--c) var(--mix-hover), black);
}
[data-variant]:active {
background: color-mix(in oklch, var(--c) var(--mix-active), black);
}
[data-variant="primary"] { --c: #3b82f6; }
[data-variant="success"] { --c: #10b981; }
[data-variant="danger"] { --c: #ef4444; }
[data-variant="warning"] { --c: #f59e0b; color: #111; }
Theme generation from a single color
:root {
--base: #6366f1; /* Indigo — change this one value */
--bg: color-mix(in oklch, var(--base) 5%, #0f1117);
--surface: color-mix(in oklch, var(--base) 10%, #1a1d27);
--border: color-mix(in oklch, var(--base) 15%, #2a2e3a);
--accent: color-mix(in oklch, var(--base) 90%, white);
--muted: color-mix(in oklch, var(--base) 30%, gray);
}
Accessible contrast for text on colored backgrounds
.tag {
--tag-color: #3b82f6;
background: color-mix(in oklch, var(--tag-color) 15%, white);
color: color-mix(in oklch, var(--tag-color) 70%, black);
/* Light background with dark text ensures readable contrast */
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.875rem;
}
/* Dark mode: reverse the mix targets */
@media (prefers-color-scheme: dark) {
.tag {
background: color-mix(in oklch, var(--tag-color) 20%, #0f1117);
color: color-mix(in oklch, var(--tag-color) 40%, white);
}
}
Gradient with harmonious midpoints
/* srgb gradients between complementary colors often look muddy */
.gradient-old {
background: linear-gradient(to right, blue, orange);
}
/* Use color-mix to create a pleasing midpoint */
.gradient-better {
--mid: color-mix(in oklch, blue 50%, orange);
background: linear-gradient(to right, blue, var(--mid), orange);
}
14. Performance Considerations
color-mix() is resolved at computed-value time by the browser. The performance impact is minimal:
- No runtime cost: The browser computes the final color once during style resolution. It is not recalculated on every frame.
- Custom property changes: When a custom property used inside
color-mix()changes, the browser recalculates the affected elements. This is the same cost as any custom property change. - Animations: Animating a custom property that feeds into
color-mix()with@propertyregistration works, but triggers style recalculation on each frame. For smooth color transitions, prefertransition: background 0.2son the property itself. - No paint overhead:
color-mix()produces a flat color value. Unlikefilteroropacity, it does not create additional compositing layers.
/* Efficient: transition the property, not the variable */
.btn {
background: color-mix(in oklch, var(--c) 100%, black);
transition: background 0.15s ease;
}
.btn:hover {
background: color-mix(in oklch, var(--c) 80%, black);
/* Browser transitions between the two computed colors smoothly */
}
15. Best Practices
- Default to
oklch. It produces the most perceptually uniform results across the widest range of color pairs. - Combine with custom properties. Define base colors as custom properties and derive all variants with
color-mix(). One variable change updates the entire theme. - Always provide a fallback. Use the CSS cascade: put a static color before the
color-mix()declaration. Browsers that cannot parse it will ignore the second line. - Use
transparentfor alpha effects.color-mix(in srgb, #3b82f6 40%, transparent)is clearer than calculatingrgba()values manually. - Prefer
color-mix()overopacityfor color variations. Opacity affects the entire element and its children.color-mix()targets only the color value. - Keep the number of base colors small. A design system needs 3-5 base colors at most. Derive everything else with
color-mix(). - Test in both light and dark modes. Verify that your
color-mix()derived colors maintain sufficient contrast in both themes. - Use our Color Converter to verify computed color values and test contrast ratios.
For more on CSS color techniques, see our CSS Variables Complete Guide, CSS Gradients Complete Guide, and Hex Color Codes Complete Guide. For custom properties in depth, read the CSS Custom Properties Complete Guide.
Frequently Asked Questions
What is CSS color-mix() and what does it do?
CSS color-mix() is a function that blends two colors together in a specified color space. The syntax is color-mix(in <color-space>, color1 percentage, color2 percentage). It lets you create tints, shades, hover states, and entire color palettes directly in CSS without preprocessors or JavaScript. For example, color-mix(in oklch, #3b82f6 70%, white) produces a lighter variant of blue by mixing 70% blue with 30% white in the perceptually uniform oklch color space.
Which color space should I use with color-mix()?
For most use cases, oklch or oklab produce the best results because they are perceptually uniform. Use srgb for simple blends that match traditional web color behavior. Use hsl if you want predictable hue interpolation. Avoid srgb-linear for general use as it can produce unexpectedly dark midpoints. The oklch space is recommended as the default choice for modern CSS color work.
Is color-mix() supported in all major browsers?
Yes. As of 2026, color-mix() is supported in Chrome 111+, Firefox 113+, Safari 16.2+, and Edge 111+. It is considered Baseline Widely Available. For older browsers, use @supports (color: color-mix(in srgb, red 50%, blue)) to detect support and provide fallback colors.
Can I use color-mix() with CSS custom properties for theming?
Yes, and this is one of the most powerful patterns. Define a base color as a custom property like --brand: #3b82f6, then generate an entire palette using color-mix(in oklch, var(--brand) 80%, white) for lighter variants and color-mix(in oklch, var(--brand) 80%, black) for darker variants. Changing a single custom property instantly updates every derived color, making theme switching and dark mode implementation far simpler.
How does color-mix() compare to using opacity or filter for color variations?
color-mix() is superior to opacity and filter for creating color variations. Opacity affects the entire element including text and children, making content harder to read. Filter brightness or saturate changes the visual appearance but does not produce a specific target color. color-mix() creates an actual new color value that can be used anywhere a color is accepted: backgrounds, borders, text, shadows, gradients, and more. It is predictable, precise, and does not affect child elements.