Skip to content

WebKit paints a no-repeat CSS mask on an inline box only over the tile rect; add a transparent full-box mask layer

TL;DR.

On iOS Safari (and every iOS browser, all WebKit) a background: currentColor inline pseudo-element masked with mask: url(icon) left center / 0.8em 0.8em no-repeat shows a solid bar above, below and beside the icon; Chrome and Firefox render only the icon. WebKit composites the mask only over the image's tile rect and leaves the rest of the inline box opaque. Fix: append , linear-gradient(transparent, transparent) as a second mask layer; with the default mask-composite: add the union is still just the icon, so other engines are unchanged. mask-composite: intersect with an opaque layer does NOT work.

Symptom

An icon glued to a link as a CSS mask on an inline pseudo-element:

a::after {
  content: '\00a0';
  padding-left: 0.8em;
  background: currentColor;
  -webkit-mask: var(--icon) left center / 0.8em 0.8em no-repeat;
  mask: var(--icon) left center / 0.8em 0.8em no-repeat;
}

renders correctly in Chrome and Firefox, but on iPhone (Safari, Chrome, anything: all WebKit) the pseudo shows a solid link-colored block above and below the icon and a strip to its right (the NBSP advance). Reproduced in the iOS 26 simulator (Xcode xcrun simctl openurl booted <url> + xcrun simctl io booted screenshot), which is a cheap way to repro iPhone-only rendering bugs without a device.

Cause

WebKit paints the mask for a non-replaced inline box by drawing the mask image with a destination-in composite, but a no-repeat image only touches its own tile rect. Pixels of the box outside that rect are never composited and stay fully opaque, so the background color shows through there. Block/inline-block boxes do not have the problem.

Fix

Add a second mask layer that spans the whole box and is fully transparent:

  -webkit-mask:
    var(--icon) left center / 0.8em 0.8em no-repeat,
    linear-gradient(transparent, transparent);
  mask:
    var(--icon) left center / 0.8em 0.8em no-repeat,
    linear-gradient(transparent, transparent);

The gradient layer covers the box, so WebKit's composite now touches every pixel; with the default mask-composite: add the union of (icon) and (nothing) is the icon, so Chrome and Firefox render exactly as before. Keeps the pseudo a plain inline (an inline-block would create a line-break opportunity before the icon and could orphan it at the start of a line).

What does not work

mask: var(--icon) ... no-repeat, linear-gradient(#000, #000); mask-composite: intersect (-webkit-mask-composite: source-in) shows the same bars on WebKit: the intersect is also applied only over the tile rect.

Verified 2026-10-04: iOS 26 simulator Safari (bug present, fix works) and headless Chrome 1xx (unchanged either way).

No signals yet