rgba() → color-mix(): the v4 opacity revolution
In v3, an opacity modifier like bg-cyan-500/40 compiled to
rgba(34, 211, 238, 0.4) — a hardcoded 4-tuple. v4 replaces
this with native CSS color-mix():
color-mix(in oklch, var(--color-cyan-500) 40%, transparent).
The color stays a CSS variable (so themes can swap it live), and OKLCH
blending means the fade-to-transparent path
is perceptually linear — no muddy mid-tones.
| aspect | v3 rgba() | v4 color-mix() |
|---|---|---|
| compiled output | rgba(34,211,238,0.4) |
color-mix(in oklch, var(--color-cyan-500) 40%, transparent) |
| color space | sRGB (gamma-encoded) | OKLCH (perceptually uniform — fade looks even to the eye) |
| theme-swap friendly | ❌ opacity baked into a literal rgba — must regenerate per theme | ✅ reads var(--color-*) — swap the var, every /40 follows |
| also works for | bg only (text/border had separate / impls) |
any color utility: text-*, border-*, ring-*, divide-*, from-*… |
| browser floor | every browser ever | color-mix(): Chrome 111+, Safari 16.2+, Firefox 113+ (all 2023+) |
1 · opacity playground — pick a color, drag opacity, see color-mix()
Pick a Tailwind v4 color token and drag the opacity slider. The preview
swatch applies the actual color-mix(in oklch, …) via
inline style (so it's instant), and the readout shows the equivalent
Tailwind utility, the compiled CSS, and what your browser resolves the
computed value to.
2 · layered transparency — where color-mix() actually shines
Each block below is a Tailwind opacity utility. Where they overlap, the
browser composites them — the same math color-mix() uses
internally. OKLCH keeps the overlaps perceptually even (no brown mud
where red + green meet, which sRGB rgba blending produces).
Try it in v3 mental model: rgba(239,68,68,0.5) over
rgba(34,197,94,0.5) over rgba(59,130,246,0.5)
— the overlap desaturates toward gray. v4's OKLCH path stays vivid.
opacity on text & border too
The /opacity suffix works on every color utility —
not just bg-*. One syntax, every property.
bg-cyan-500/20 · border-cyan-500/60 · text-cyan-900/80
three opacities, three properties, one syntax — all color-mix().
3 · arbitrary opacity — when /40 isn't granular enough
Tailwind ships /5 /10 /20 … /95 as named steps. Need exactly
33%? Use the arbitrary-value form: /[0.33] (number) or
/[33%] (percentage). Both compile to the same color-mix.
| intent | utility | compiled (abridged) |
|---|---|---|
| named opacity step | bg-cyan-500/40 |
color-mix(in oklch, var(--color-cyan-500) 40%, transparent) |
| arbitrary decimal (0–1) | bg-cyan-500/[0.33] |
color-mix(in oklch, var(--color-cyan-500) 33%, transparent) |
| arbitrary percentage | bg-cyan-500/[33%] |
color-mix(in oklch, var(--color-cyan-500) 33%, transparent) |
| fully opaque (explicit) | bg-cyan-500/100 |
color-mix(in oklch, var(--color-cyan-500) 100%, transparent) |
| fully transparent | bg-cyan-500/0 |
color-mix(in oklch, var(--color-cyan-500) 0%, transparent) |
4 · beyond opacity — color-mix() blends two colors
color-mix() isn't just for transparency. Drop the
transparent and pass a second color — you get a true blend.
Useful for gradient stops, hover tints, and generated palettes inside
@utility or a <style type="text/tailwindcss"> block.
The in oklch keyword controls the interpolation color
space. For gradients this matters a lot — see
gradients_v4.
intent → pattern
| intent | pattern | why |
|---|---|---|
| semi-transparent background | bg-cyan-500/40 |
compiles to color-mix(in oklch, var(--color-cyan-500) 40%, transparent) |
| non-standard opacity | bg-cyan-500/[0.33] |
arbitrary decimal — escapes the named /5 /10 /20 … scale |
| opacity on text or border | text-red-500/70 · border-green-500/30 |
same /opacity suffix — works on any color utility |
| ring/divide/from opacity | ring-cyan-500/50 · from-blue-500/30 |
every color-bearing utility accepts the modifier |
| blend two theme colors | color-mix(in oklch, var(--color-a), var(--color-b) 50%) |
generates a perceptually correct mid-stop — great for gradients |
| dynamic opacity from JS | style.backgroundColor = 'color-mix(in oklch, ' + oklch + ' ' + pct + '%, transparent)' |
use the OKLCH token value directly — no rgba recomputation |