color-mix() vs opacity — When to Use Which
Understand the practical difference between mixing a color toward transparent and using the opacity property. Includes accessibility and stacking implications.
Detailed Explanation
Two Ways to "Fade" a Color, Two Different Effects
It is tempting to think opacity: 0.5 and color-mix(in oklch, c, transparent 50%)
do the same thing. They do not.
opacity: 0.5
.card { background: blue; opacity: 0.5; }
This fades the entire element, including all its descendants — text, icons, borders, child components. It also creates a new stacking context. Useful for "ghost" overlays but disastrous for accessibility: text inside the element fails contrast checks even when the underlying color choice was fine.
color-mix(... transparent 50%)
.card { background: color-mix(in oklch, blue, transparent 50%); }
This fades only the background. Text and icons inside the element keep their full alpha. No stacking-context surprise.
A practical decision tree
| Goal | Use |
|---|---|
| Fade out a whole pop-over during a transition | opacity |
| Show a translucent header background | color-mix(... transparent ...) |
| Disabled state for an entire button group | opacity |
| Disabled background fill, but keep label readable | color-mix(... transparent ...) |
| 50% chip background tint | color-mix(... transparent ...) |
Bonus: alpha math via Relative Color Syntax
If you want to take an existing color and halve its alpha precisely, Relative Color Syntax gives you direct access:
.faded { color: oklch(from var(--brand) l c h / calc(alpha * 0.5)); }
Use Case
Refactoring legacy code that uses `opacity` for visual fading where it is producing accessibility regressions. Choosing the right primitive for translucent UI surfaces.