TIL: Tailwind v4 mask utilities need the webkit prefix in plain CSS
Porting a Tailwind mask-composite utility to scoped CSS quietly breaks in Chrome and Safari unless you also write the -webkit-mask-* properties, because Tailwind was emitting them for you.
I had a border-beam effect (an animated gradient clipped to a card's border) built with Tailwind v4 utilities. It looked right. When I moved it into plain scoped CSS to make it a standalone component, the whole gradient rendered as a solid wedge over the card instead of a thin beam on the border.
The utilities I ported looked roughly like this:
<div
class="![mask-clip:padding-box,border-box] ![mask-composite:intersect] [mask:linear-gradient(#fff_0_0)_padding-box,linear-gradient(#fff_0_0)]"
></div>
My scoped-CSS version wrote the standard properties faithfully:
.beam {
mask:
linear-gradient(#fff 0 0) padding-box,
linear-gradient(#fff 0 0);
mask-clip: padding-box, border-box;
mask-composite: intersect;
}
Why it broke
Tailwind v4 does not just emit the standard mask-composite: intersect. It also emits the WebKit-prefixed equivalent, -webkit-mask-composite: source-in, alongside it. WebKit engines (both Chrome and Safari) still rely on the prefixed property for mask compositing. My hand-written CSS only had the unprefixed version, so those browsers silently ignored the compositing step, the two mask layers never intersected, and the border clip disappeared.
The fix
Write the prefixed properties too:
.beam {
mask:
linear-gradient(#fff 0 0) padding-box,
linear-gradient(#fff 0 0);
-webkit-mask:
linear-gradient(#fff 0 0) padding-box,
linear-gradient(#fff 0 0);
mask-clip: padding-box, border-box;
-webkit-mask-clip: padding-box, border-box;
mask-composite: intersect;
-webkit-mask-composite: source-in;
}
Note the value difference: the standard mask-composite: intersect maps to -webkit-mask-composite: source-in, not intersect. You can confirm it worked by reading back getComputedStyle(el).webkitMaskComposite, which should return source-in.
The general lesson: when a Tailwind utility "just works" and your hand-rolled CSS version does not, check what Tailwind actually compiled the utility to. It frequently emits vendor-prefixed fallbacks that you have to reproduce yourself once you leave the utility layer.