color-mix()
The Problem
Creating tints, shades, and semi-transparent color variants in CSS traditionally requires manually calculating each color value. AI agents frequently hard-code every shade as a separate hex or rgb value (e.g., generating #3366cc, #5588dd, #99bbee independently), making palettes brittle and difficult to maintain. When the base brand color changes, every derived value must be recalculated. CSS preprocessors like Sass solved this with lighten() and darken(), but native CSS had no equivalent — until color-mix().
The Solution
color-mix() is a CSS function that blends two colors in a specified color space and returns the resulting color. It takes a color space, two colors, and optional percentage values that control the mix ratio.
color-mix(in <color-space>, <color> <percentage>?, <color> <percentage>?)
The color space parameter determines how the interpolation is calculated. Using oklch or oklab produces more perceptually uniform blends than srgb.
Code Examples
Basic Syntax
:root {
--brand: oklch(55% 0.25 264);
/* Mix 70% brand with 30% white = a lighter tint */
--brand-light: color-mix(in oklch, var(--brand) 70%, white);
/* Mix 70% brand with 30% black = a darker shade */
--brand-dark: color-mix(in oklch, var(--brand) 70%, black);
/* Equal mix of two colors */
--blend: color-mix(in oklch, var(--brand), orange);
}
Creating Tints and Shades from a Single Base Color
:root {
--brand: oklch(50% 0.22 264);
/* Tints (lighter) — mixing with white */
--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);
/* Shades (darker) — mixing with black */
--brand-600: color-mix(in oklch, var(--brand) 80%, black);
--brand-700: color-mix(in oklch, var(--brand) 60%, black);
--brand-800: color-mix(in oklch, var(--brand) 40%, black);
--brand-900: color-mix(in oklch, var(--brand) 25%, black);
}
Transparent Variants
Mixing with transparent creates semi-transparent versions of any color, which is especially useful for hover states, overlays, and backgrounds:
:root {
--brand: oklch(55% 0.25 264);
/* Semi-transparent variants */
--brand-alpha-10: color-mix(in oklch, var(--brand) 10%, transparent);
--brand-alpha-20: color-mix(in oklch, var(--brand) 20%, transparent);
--brand-alpha-50: color-mix(in oklch, var(--brand) 50%, transparent);
}
.hover-card:hover {
background-color: var(--brand-alpha-10);
}
.overlay {
background-color: var(--brand-alpha-50);
}
.subtle-border {
border-color: var(--brand-alpha-20);
}
Interactive State Colors
:root {
--btn-bg: oklch(55% 0.22 264);
--btn-hover: color-mix(in oklch, var(--btn-bg), white 15%);
--btn-active: color-mix(in oklch, var(--btn-bg), black 15%);
--btn-disabled: color-mix(in oklch, var(--btn-bg) 40%, oklch(70% 0 0));
}
.button {
background: var(--btn-bg);
}
.button:hover {
background: var(--btn-hover);
}
.button:active {
background: var(--btn-active);
}
.button:disabled {
background: var(--btn-disabled);
}
Color Space Comparison
:root {
--red: oklch(60% 0.25 30);
--blue: oklch(55% 0.25 264);
/* sRGB interpolation — can produce muddy, desaturated results */
--mix-srgb: color-mix(in srgb, var(--red), var(--blue));
/* oklch interpolation — maintains vibrancy through the blend */
--mix-oklch: color-mix(in oklch, var(--red), var(--blue));
}
Dynamic Theme with a Single Base Color
:root {
--base: oklch(55% 0.2 264);
--surface: color-mix(in oklch, var(--base) 5%, white);
--surface-raised: color-mix(in oklch, var(--base) 10%, white);
--border: color-mix(in oklch, var(--base) 20%, oklch(80% 0 0));
--text: color-mix(in oklch, var(--base) 40%, black);
--text-muted: color-mix(in oklch, var(--base) 30%, oklch(50% 0 0));
--accent: var(--base);
--accent-hover: color-mix(in oklch, var(--base), white 20%);
}
Blending Adjacent Color Tokens
:root {
--success: oklch(60% 0.2 145);
--warning: oklch(70% 0.2 85);
/* Blend between semantic colors for status transitions */
--status-improving: color-mix(in oklch, var(--warning) 60%, var(--success));
}
Common AI Mistakes
- Hard-coding every color shade as a separate hex value instead of deriving tints and shades from a single base with
color-mix() - Using
in srgbfor blending whenin oklchorin oklabproduces more visually uniform results — sRGB blending creates muddy midpoints especially between saturated colors - Omitting the color space parameter entirely — it is required by the specification
- Confusing the percentage semantics:
color-mix(in oklch, red 70%, blue)means 70% red and 30% blue, not adding 70% of blue - Reaching for JavaScript or CSS preprocessors (
sass darken(),lighten()) for color manipulation thatcolor-mix()handles natively - Not realizing that mixing with
transparentcreates a semi-transparent version of the color — AI agents often still usergba()or manual alpha values - Using
color-mix()where a simpleroklch()value with adjusted lightness would be clearer and more maintainable
When to Use
- Design systems: Derive an entire shade scale from a single brand color
- Interactive states: Generate hover, active, focus, and disabled color variants systematically
- Transparent overlays: Create alpha variants of semantic colors without hard-coding rgba values
- Theme customization: Let users pick a base color and derive all UI colors from it
- Blending semantic tokens: Mix between success/warning/danger colors for status transitions
When not to use
- For simple static colors that don't need to relate to a base — just use
oklch()orhexdirectly - When every shade needs precise, designer-specified values that don't follow a simple mix with white/black
- For critical branded colors that must match exact specifications (mix results depend on the interpolation color space)