Deriving a hover state, a tint, or a shade from a base color used to require Sass's darken()/lighten() or a JavaScript color library. color-mix() brings that capability natively into the browser, with a choice of color space that genuinely affects the result.
1Syntax: Color Space, Colors, And Weights
color-mix() takes a required color space declaration (in srgb, in oklch, in hsl, and others), followed by two colors to blend. Without explicit weights, the mix defaults to 50/50. Adding a percentage after either color sets that color's weight in the blend — the two percentages don't need to add up to 100%, but if only one is specified, the other is inferred as the remainder.
This single function replaces what used to require either precomputing every tint and shade ahead of time in a preprocessor, or writing custom JavaScript color-manipulation logic just to produce a hover or active state at runtime.
2The Real Win: Deriving States From Design Tokens
The most valuable pattern isn't blending two hardcoded colors — it's blending a design token with black or white to derive a hover, active, or disabled state that automatically tracks the token. color-mix(in srgb, var(--color-brand) 85%, black) produces a slightly darkened brand color for a hover state, and critically, if --color-brand changes during a rebrand, this hover state recalculates correctly without anyone touching it.
This directly extends the design-token philosophy from the Scalable CSS lesson: instead of hardcoding a second, separately-maintained hover color, you derive it mathematically from the single source of truth, eliminating an entire category of 'the hover state wasn't updated after the rebrand' bugs.
3The Color Space Argument Isn't Just A Formality
It's tempting to treat in srgb as boilerplate you always include without thinking about it, but the color space genuinely changes how the two colors are interpolated, not merely how precisely the result is represented. Mixing red and lime green directly in sRGB tends to pass through a dull, desaturated brownish-yellow midpoint. Mixing the same two colors in oklch, a perceptually-uniform color space, produces a smoother, more vivid transition that better matches human color perception.
As a practical default: use oklch when the visual smoothness of a gradient-like blend matters (like generating a tint/shade scale), and srgb when you need the mix to behave predictably alongside other sRGB-authored colors in a legacy palette.
4Step-by-Step Breakdown
Blending Colors, Natively. Need a color 20% lighter, or a hover state that's your brand color mixed with black? For years that meant reaching for a Sass function or a JavaScript color-manipulation library. color-mix() does it directly in CSS, live, in the browser, in your choice of color space.
The Basic Syntax: Color Space, Then Colors And Weights. color-mix(in srgb, red, blue) blends two colors 50/50 by default, in the specified color space. Adding a percentage after either color adjusts the mix ratio — color-mix(in srgb, red 70%, blue) weights the result toward red.
color-mix() Syntax. In color-mix(in srgb, red 70%, blue), what does the 70% control?
- →The opacity of the final mixed color
- →How much red contributes to the final blend, relative to blue
- →The lightness value of red specifically
Mixing With Tokens Instead Of Hardcoded Colors. color-mix()'s biggest practical win is that its arguments can be CSS custom properties, letting you derive an entire palette of tints and shades from one brand token at runtime, instead of hardcoding every variant by hand or precomputing them in a preprocessor.
Deriving States From Tokens. What's the benefit of writing a hover state as color-mix(in srgb, var(--color-brand) 85%, black) instead of a separate hardcoded hex value?
- →It renders measurably faster than a hardcoded color
- →If --color-brand changes (e.g. a rebrand), the hover state automatically stays proportionally darker without a separate update
- →There's no practical difference
Choosing A Color Space Changes The Result. The color space named after in genuinely changes the mixed output, not just its precision — mixing in oklch tends to produce perceptually smoother, more even transitions than mixing in srgb, especially for colors far apart on the color wheel, where sRGB mixing can produce a muddy, desaturated middle.
Color Space Choice Matters. Why might color-mix(in oklch, red, lime) look meaningfully different from color-mix(in srgb, red, lime)?
- →Only due to minor floating-point rounding differences
- →Because different color spaces interpolate between colors differently, and oklch is designed to be more perceptually uniform
- →It's inconsistent browser rendering, not a real difference
Native Color Blending Unlocked. You can now blend colors natively in CSS, derive hover and state variants directly from design tokens so they stay in sync automatically, and choose a color space deliberately based on the perceptual quality of the blend you actually want.
Mix Two Colors Together. color-mix() blends two colors in a given color space, without you calculating the result by hand.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Derived Color-Mix States Must Still Be Manually Verified For Contrast
A hover state computed via color-mix() is only as accessible as its resulting contrast ratio — automatically deriving a color doesn't automatically guarantee it passes WCAG contrast requirements against its background or text.
2oklch-Based Mixing Can Produce More Perceptually Consistent Lightness Steps For Accessible Palettes
Because oklch models perceived lightness more accurately than sRGB, generating a tint/shade scale in oklch tends to produce steps that are more evenly perceptually spaced, which is useful when building an accessible, systematic color scale.
SEO Implications
- 1
Deriving Palettes At Runtime Reduces The Number Of Hardcoded Color Declarations Shipped
Generating tints and shades via color-mix() instead of authoring dozens of precomputed hex values can measurably shrink a design system's CSS footprint.
- 2
Eliminating A JavaScript Color Library Reduces Bundle Size And Main-Thread Work
Projects that previously used a JS library purely for color manipulation (darken/lighten logic) can often remove that dependency entirely, improving load performance metrics that factor into search ranking signals.
Best Practices
Derive Interactive States (Hover, Active, Disabled) From Base Tokens Instead Of Hardcoding Them
This keeps every derived state automatically correct after a rebrand or theme change, eliminating a whole category of stale-color bugs that hardcoded hex values are prone to.
Default To oklch For Generating Systematic Tint/Shade Scales
Its perceptual uniformity produces visually smoother, more predictable results than sRGB when blending colors that are far apart on the color wheel.
Frequent Bugs
A hover state color looks correct after launch but becomes visually wrong after a brand color update.
The hover state was a separately hardcoded hex value instead of a color-mix() derived from the brand token — switch to deriving it so future token updates propagate automatically.
A generated tint/shade scale looks muddy or uneven in the middle of the range.
The mix was computed in srgb; switch to color-mix(in oklch, ...) for a perceptually smoother result across the scale.
Real-World Examples
A Full Button State Set Derived From One Token
A button component deriving its default, hover, and active background colors entirely from a single --color-brand custom property using color-mix().
:root { --color-brand: #FF0099; }
.btn { background: var(--color-brand); }
.btn:hover { background: color-mix(in srgb, var(--color-brand) 85%, black); }
.btn:active { background: color-mix(in srgb, var(--color-brand) 70%, black); }