Blog 3 min read

text-white/50 and opacity-50 are not the same thing

Tailwind gives you two ways to fade something: an alpha channel on the color, or opacity on the element. They composite differently, and the difference shows up as double-dark strikethroughs, mismatched icons, and borders that won't fade. Here's the mental model.

Here’s a small mystery from a pricing page. A struck-through old price, faded to 50%:

html
<del class="text-foreground/50">$199</del>

Look closely at the result and something is off: where the strikethrough line crosses the digits, the overlap is visibly darker than either the line or the text. The element doesn’t look faded; it looks like two translucent things stacked on top of each other.

Because that’s exactly what it is.


Alpha fades each paint, opacity fades the result

text-foreground/50 doesn’t fade the element. It sets color to a value with a 50% alpha channel — and color is used by several paints independently: the glyphs, and any text decorations (line-through, underline), which default to currentColor.

The browser paints the semi-transparent glyphs, then paints the semi-transparent line on top of them. Where they overlap, the alphas stack: 50% over 50% composites to about 75% opaque. Hence the dark intersections — each paint is faded, but the overlaps add up.

opacity-50 works at a different stage. The element renders fully opaque — text, decoration, background, borders, children, everything — and then the finished composite is faded as one layer. No overlaps can add up, because by the time opacity applies there’s only one picture:

html
<del class="text-foreground opacity-50">$199</del>

Same 50%, uniform fade, mystery gone.


The mental model

  • Color alpha (text-white/50, bg-black/20, border-white/10): “make this property’s paint translucent.” Surgical. Only the paints that read that property fade; everything else stays put.
  • Opacity (opacity-50): “make the rendered element translucent.” Global. Everything the element and its children draw fades together, after compositing.

Neither is the better one; they answer different questions. You want alpha when fading one ingredient: a subtle border (border-white/10), a tinted overlay (bg-black/40), muted text on a colored card where the icon next to it must stay vivid. You want opacity when the whole thing should recede as a unit: a disabled button, a dimmed card, our strikethrough price — anything where text, decorations, and icons must fade in step.


The classic symptoms

Once you know the compositing difference, a family of small bugs collapses into one explanation:

  • Double-dark strikethroughs and underlines. Decoration over glyphs, both translucent, overlaps stack. Use opacity-*, or keep alpha on the text and set an explicit decoration-* color.
  • Stacked translucent layers going darker than intended. Two bg-white/50 siblings overlapping composite to 75%. Restructure, or fade the parent once with opacity.
  • “Why won’t my child un-fade?” — the reverse trap. opacity on a parent applies to the finished composite, so a child can never climb back to full opacity from inside. If one child must stay vivid, fade the others with color alpha instead of fading the parent.
  • Icons and text fading unevenly. An SVG icon stroked with currentColor picks up your text alpha, but one with hardcoded fills doesn’t. Opacity on the wrapper fades both identically.

One more practical difference: opacity creates a new stacking context, which occasionally reshuffles z-index expectations; color alpha never does. And if you’re animating a fade, prefer opacity — it’s GPU-composited and cheap, while animating colors re-paints.

Fade one ingredient: alpha. Fade the finished thing: opacity. That single sentence is 90% of the decision.

/Michael Andreuzza