CSS

CSS mask: Fade, Reveal & Shape Elements (Complete 2026 Guide)

W
W3Tweaks Team
Frontend Tutorials
Jun 8, 2026 21 min read
CSS mask: Fade, Reveal & Shape Elements (Complete 2026 Guide)
CSS mask uses another image or gradient as a stencil for element visibility. Unlike clip-path's hard edges, mask creates soft fades, partial transparency, and graduated reveals. This 2026 guide covers the alpha vs luminance reality (not just black=visible), gradient masks, SVG icon recoloring with currentColor, scroll container edge fades, spotlight hover, wipe reveals, scroll-driven animation-timeline reveals, mask-composite + mask-border, and the -webkit-mask-composite naming mismatch that silently breaks Safari.

CSS mask controls element visibility using another image or gradient as the “stencil”. Unlike CSS clip-path which creates hard binary edges — fully visible or fully hidden — mask creates soft fades, partial transparency, and graduated visibility.

/* Fade the bottom of an image */
.hero-img {
  mask-image: linear-gradient(to bottom, black 60%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, black 60%, transparent 100%);
}

/* Recolor an SVG icon with currentColor */
.icon {
  background-color: currentColor;
  mask-image: url('icons/arrow.svg');
  -webkit-mask-image: url('icons/arrow.svg');
}

This 2026 guide covers the complete CSS mask image property set, the alpha vs luminance reality that fixes the misleading “black = visible” rule, CSS mask gradient patterns (linear, radial, conic), SVG icon recoloring with currentColor + mask-image, CSS scroll fade gradient containers, CSS spotlight effect with mask + mousemove, CSS wipe reveal animation via @keyframes, the new scroll-driven mask reveal using animation-timeline: view(), mask-composite with the four-edge feathering pattern, the parallel mask-border property family, and the critical -webkit-mask-composite naming mismatch that silently breaks Safari. For hard-edged shape clipping without fades, use CSS clip-path instead.

Live Demo

Live Demo Open in tab

Three tabs: ① live mask builder with 6 mask types and real-time code generation, side-by-side mask vs clip-path comparison, ② six real patterns — scrollable container edge fade, spotlight hover, wipe reveal animation, diagonal fade, vignette, text edge fade, ③ mask-composite values with before/after demos and the critical -webkit prefix naming mismatch table.

How CSS mask Actually Works — The Two Rules Most Tutorials Get Wrong

Most CSS mask tutorials open with “black = visible, transparent = hidden” as if it’s a universal rule. It isn’t. That framing breaks the moment a reader swaps black for any other color and the mask still works. There are actually two distinct modes with two different rules.

Rule 1 — Alpha mode (the default for CSS gradients)

The alpha channel of each mask pixel controls visibility. Color is irrelevant.

  • rgba(0, 0, 0, 1) (opaque black) → fully visible
  • rgba(255, 0, 0, 1) (opaque red) → fully visible (same as black!)
  • rgba(255, 255, 255, 1) (opaque white) → fully visible (same as black!)
  • rgba(*, *, *, 0) (any color, zero alpha) → fully hidden
  • rgba(*, *, *, 0.5) (any color, 50% alpha) → 50% visible

We use black in gradient examples because the CSS keyword transparent is internally rgba(0, 0, 0, 0) — so black ↔ transparent creates a smooth alpha transition that visually mirrors the effect. But red ↔ transparent produces identical output.

/* These three produce IDENTICAL output in alpha mode */
mask-image: linear-gradient(to bottom, black, transparent);
mask-image: linear-gradient(to bottom, red, transparent);
mask-image: linear-gradient(to bottom, hotpink, transparent);

Rule 2 — Luminance mode (default for SVG <mask> elements)

The brightness of each mask pixel controls visibility. Color matters now.

  • Black → fully hidden
  • White → fully visible
  • Grey → partial visibility (proportional to brightness)
.svg-masked {
  mask-image: url('#svg-mask');
  mask-mode: luminance;
  -webkit-mask-mode: luminance;
}

Why two modes exist: Alpha mode matches how background-image thinks (transparency = nothing there). Luminance mode matches how Photoshop / SVG <mask> think (brightness = how much shows through). CSS gradients default to alpha because that’s the friendlier model for typical web use cases. SVG <mask> defaults to luminance because that’s the SVG specification’s original behavior.

The TL;DR table:

ModeDefault forWhat controls visibility
AlphaCSS gradients, PNGsAlpha channel of each pixel (color ignored)
LuminanceSVG <mask> elementsBrightness of each pixel

CSS mask vs clip-path: which one do you need?

This is the most-searched question about CSS masking:

clip-pathmask
Edge typeHard, sharp, binarySoft, graduated, partial
Transparency levelsNone — in or outFull range 0–100%
PerformanceFasterSlightly heavier
SVG shapesclip-path: url(#svg)mask-image: url(#svg)
Gradient shapes❌ No✅ Yes
Animate revealsHarder (vertex count rule)✅ Animate gradient stops
Browser supportBetterGood (needs prefix)
Use forSharp geometric shapesFades, spotlights, gradient reveals

Decision rule: If you need a sharp geometric shape (octagon, star, polygon cut), use clip-path. If you need any kind of soft edge, gradient fade, partial transparency, or scroll-based reveal, use mask.

The CSS mask image Properties

mask is shorthand covering 8 individual properties:

/* Shorthand */
.el {
  mask: <mask-image> <position> / <size> <repeat> <clip> <origin> <composite> <mode>;
}

/* Individual properties */
.el {
  mask-image: linear-gradient(to bottom, black, transparent);
  mask-position: 0% 0%;
  mask-size: auto;
  mask-repeat: no-repeat;
  mask-clip: border-box;
  mask-origin: border-box;
  mask-composite: add;
  mask-mode: match-source;
}

In practice you’ll set mask-image (and the webkit prefix) for almost everything. The others matter for advanced compositions.

CSS mask gradient — linear, radial, conic

Any CSS gradient works as a mask image.

Linear gradient — fade an edge

.fade-bottom {
  mask-image: linear-gradient(to bottom, black 60%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, black 60%, transparent 100%);
}

/* Fade both horizontal edges — for scroll containers */
.scroll-fade {
  mask-image: linear-gradient(to right, transparent, black 15%, black 85%, transparent);
  -webkit-mask-image: linear-gradient(to right, transparent, black 15%, black 85%, transparent);
}

Radial gradient — spotlight or vignette

.vignette {
  mask-image: radial-gradient(ellipse at center, black 40%, transparent 80%);
  -webkit-mask-image: radial-gradient(ellipse at center, black 40%, transparent 80%);
}

.spotlight {
  mask-image: radial-gradient(circle 80px at 50% 50%, black 60%, transparent 100%);
  -webkit-mask-image: radial-gradient(circle 80px at 50% 50%, black 60%, transparent 100%);
}

Conic gradient — pie wedge masks

.pie-wedge {
  mask-image: conic-gradient(from 0deg, black 0deg 90deg, transparent 90deg 360deg);
  -webkit-mask-image: conic-gradient(from 0deg, black 0deg 90deg, transparent 90deg 360deg);
}

Recolor SVG Icons with mask-image and currentColor

The #1 practical reason to use mask in 2026: theming single-color SVG icons that follow currentColor (and therefore your dark mode, hover state, link color, anything).

<button class="icon-btn">
  <span class="icon icon-arrow"></span>
  Click me
</button>
.icon {
  display: inline-block;
  width: 1em;
  height: 1em;
  background-color: currentColor;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: contain;
  mask-size: contain;
}

.icon-arrow {
  -webkit-mask-image: url('/icons/arrow.svg');
  mask-image: url('/icons/arrow.svg');
}

.icon-btn { color: blue; }            /* icon is blue   */
.icon-btn:hover { color: red; }       /* icon is red    */
.dark .icon-btn { color: white; }     /* icon is white  */

Why this beats <img src="arrow.svg">: the SVG file is loaded once and recolored infinitely via CSS — no per-color SVG copies, no inline SVG bloat in your HTML, no JavaScript. The icon’s color is whatever the surrounding text color is.

Limitation: the source SVG must be single-color (any color in the file is replaced by the background-color you set). Multi-color icons need a different approach (inline SVG with fill: currentColor on specific paths, or CSS filter chains).

Pattern 1: CSS scroll fade gradient — fade scroll container edges

The #1 practical use case for mask-image. Horizontal or vertical scrollable containers that hint at more content by fading the edges:

/* Horizontal scroll with faded edges */
.chips-scroll {
  display: flex;
  overflow-x: auto;
  gap: 8px;
  padding: 8px 16px;
  scrollbar-width: none;

  mask-image: linear-gradient(to right, transparent, black 15%, black 85%, transparent);
  -webkit-mask-image: linear-gradient(to right, transparent, black 15%, black 85%, transparent);
}

.chips-scroll::-webkit-scrollbar { display: none; }

Feathering all 4 edges with mask-composite: intersect

To fade all four edges of a container (top + bottom + left + right), stack two linear gradients and combine them with mask-composite: intersect:

.four-edge-fade {
  /* Two gradients: horizontal fade + vertical fade */
  mask-image:
    linear-gradient(to right, transparent, black 10%, black 90%, transparent),
    linear-gradient(to bottom, transparent, black 10%, black 90%, transparent);
  mask-composite: intersect;

  -webkit-mask-image:
    linear-gradient(to right, transparent, black 10%, black 90%, transparent),
    linear-gradient(to bottom, transparent, black 10%, black 90%, transparent);
  -webkit-mask-composite: source-in;
}

The intersect composite means a pixel is only visible where BOTH gradients allow it — which is the center area of the box, with all four edges fading out simultaneously.

Pattern 2: CSS spotlight effect with mask + mousemove

A dark overlay with a radial gradient mask centered at the cursor position — the element “lights up” where the mouse is:

<div class="card-wrap" id="card">
  <div class="card-content"><!-- content --></div>
  <div class="spotlight-overlay" id="overlay"></div>
</div>
.card-wrap { position: relative; overflow: hidden; }

.spotlight-overlay {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.85);
  opacity: 0;
  transition: opacity 0.2s;
  mask-image: radial-gradient(circle 80px at var(--x, 50%) var(--y, 50%), black 60%, transparent 100%);
  -webkit-mask-image: radial-gradient(circle 80px at var(--x, 50%) var(--y, 50%), black 60%, transparent 100%);
}

.card-wrap:hover .spotlight-overlay { opacity: 1; }
const card = document.getElementById('card');
const overlay = document.getElementById('overlay');

card.addEventListener('mousemove', (e) => {
  const rect = card.getBoundingClientRect();
  overlay.style.setProperty('--x', `${e.clientX - rect.left}px`);
  overlay.style.setProperty('--y', `${e.clientY - rect.top}px`);
});

Using CSS custom properties (--x, --y) means the mask gradient string is only declared once in CSS — JS just updates two custom properties, which is far cheaper than rewriting the entire mask-image value on every mousemove event.

Touch device fallback: hover doesn’t exist on touch devices. For mobile, either skip the spotlight entirely with @media (hover: hover) or tie the position to a tap point:

@media (hover: none) {
  .spotlight-overlay { opacity: 0 !important; }
}

Pattern 3: CSS wipe reveal animation with @keyframes

Animating a mask gradient’s stop position creates a smooth wipe reveal — no clip-path vertex counting needed:

@keyframes wipe-in {
  from { mask-image: linear-gradient(to right, black 0%, transparent 0%);
         -webkit-mask-image: linear-gradient(to right, black 0%, transparent 0%); }
  to   { mask-image: linear-gradient(to right, black 100%, transparent 100%);
         -webkit-mask-image: linear-gradient(to right, black 100%, transparent 100%); }
}

.reveal { animation: wipe-in 1s cubic-bezier(0.4, 0, 0.2, 1) forwards; }

@media (prefers-reduced-motion: reduce) {
  .reveal { mask-image: none; -webkit-mask-image: none; animation: none; }
}

Pattern 4: Scroll-driven mask reveal (animation-timeline)

The 2026-defining technique. Tie a mask reveal to scroll position with zero JavaScript using CSS Scroll-Driven Animations (Chrome 115+, Firefox behind flag):

@keyframes scroll-reveal {
  from { mask-image: linear-gradient(to right, black 0%, transparent 0%);
         -webkit-mask-image: linear-gradient(to right, black 0%, transparent 0%); }
  to   { mask-image: linear-gradient(to right, black 100%, transparent 100%);
         -webkit-mask-image: linear-gradient(to right, black 100%, transparent 100%); }
}

.scroll-revealed {
  animation: scroll-reveal linear both;
  animation-timeline: view();           /* tied to viewport entry */
  animation-range: entry 0% cover 40%;  /* fully revealed by 40% of cover */
}

/* Fallback for browsers without scroll-driven animations */
@supports not (animation-timeline: view()) {
  .scroll-revealed {
    mask-image: linear-gradient(to right, black 100%, transparent 100%);
    -webkit-mask-image: linear-gradient(to right, black 100%, transparent 100%);
    animation: none;
  }
}

The mask animates as the element enters the viewport — wiping in from left to right as it scrolls into view. No IntersectionObserver, no scroll event listeners. animation-range controls precisely when the animation starts and ends relative to the element’s position in the scroll container.

For background scroll progress (mask tied to overall page scroll instead of element entry), use animation-timeline: scroll(root block).

Pattern 5: CSS text mask gradient

Animating a shimmer mask across headlines for hero text effects:

.shimmer-text {
  background: linear-gradient(90deg, #7c3aed, #0891b2, #059669);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;

  /* Animated shimmer mask sweeping left-to-right */
  -webkit-mask-image: linear-gradient(90deg, black 30%, white 50%, black 70%);
  mask-image: linear-gradient(90deg, black 30%, white 50%, black 70%);
  -webkit-mask-mode: luminance;
  mask-mode: luminance;
  -webkit-mask-size: 200% 100%;
  mask-size: 200% 100%;
  animation: shimmer 3s linear infinite;
}

@keyframes shimmer {
  from { -webkit-mask-position: 100% 0; mask-position: 100% 0; }
  to   { -webkit-mask-position: -100% 0; mask-position: -100% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .shimmer-text { animation: none; }
}

Note mask-mode: luminance — the gradient uses black ↔ white (a brightness ramp), so we need the luminance interpretation, not alpha.

mask-mode — Alpha vs Luminance Made Concrete

/* alpha mode — alpha controls visibility */
.alpha {
  mask-image: linear-gradient(to right, rgba(0,0,0,0), rgba(0,0,0,1));
  mask-mode: alpha; /* default for gradients */
}

/* luminance mode — brightness controls visibility */
.lum {
  mask-image: linear-gradient(to right, black, white);
  mask-mode: luminance;
  -webkit-mask-mode: luminance;
}

mask-type on the SVG <mask> element

There’s also mask-type — set on the SVG <mask> element itself (not the masked element) — that controls the default mode the SVG mask reports:

<svg width="0" height="0">
  <mask id="luminance-mask" mask-type="luminance"> <!-- explicit on the SVG side -->
    <rect width="100" height="100" fill="white"/>
  </mask>
</svg>

Precedence rule: the CSS mask-mode on the masked element wins over the SVG <mask>’s mask-type. If you set mask-mode: match-source, the SVG’s mask-type is honored. If you set mask-mode: alpha or mask-mode: luminance, the explicit CSS value wins.

Multiple Mask Layers and mask-composite

Like background-image, mask-image accepts multiple comma-separated layers:

.multi-masked {
  mask-image:
    radial-gradient(circle at 30% 50%, black 30%, transparent 50%),
    radial-gradient(circle at 70% 50%, black 30%, transparent 50%);
  mask-composite: add;

  -webkit-mask-image:
    radial-gradient(circle at 30% 50%, black 30%, transparent 50%),
    radial-gradient(circle at 70% 50%, black 30%, transparent 50%);
  -webkit-mask-composite: source-over;
}

mask-composite Values

ValueEffect
addBoth layers combined — visible wherever either allows
subtractTop layer removes from bottom — punch-out effect
intersectOnly where both layers overlap is visible
excludeXOR — overlap is hidden, non-overlap is visible

mask-composite Gotchas

Real bugs developers hit:

  1. mask-composite does nothing with a single mask layer. It only operates between 2+ layers. A single mask-image ignores mask-composite entirely.
  2. Layer order is right-to-left like background-image. The first mask listed is the TOP layer; subsequent masks are below it. mask-composite describes how layer N composites against the result of layers below it.
  3. The webkit composite is also order-sensitive but the semantics of each value name don’t fully map 1:1 — source-out describes “the top-source painted outside the destination,” which corresponds to standard subtract but is a different mental model.

⚠️ The webkit mask composite Naming Trap Explained

This is the most critical gotcha in CSS masking and is almost never documented well. The standard mask-composite values and the -webkit-mask-composite values use completely different names:

EffectStandard mask-compositePrefixed -webkit-mask-composite
Combine bothaddsource-over
Punch throughsubtractsource-out
Overlap onlyintersectsource-in
XOR ringexcludexor
/* ❌ Wrong — using standard names for both breaks Safari */
.el {
  mask-composite: subtract;
  -webkit-mask-composite: subtract; /* Safari ignores this */
}

/* ✅ Correct — different names for standard and prefixed */
.el {
  mask-composite: subtract;
  -webkit-mask-composite: source-out;
}

The webkit value source-over maps to the standard add — they are identical in effect. Same for source-out/subtract, source-in/intersect, xor/exclude. The names come from different specification ancestries — webkit kept Apple’s Quartz 2D blend operator names; the standard adopted Compositing and Blending Level 1 names.

mask-border vs mask-image — The Parallel Property Family

Just as border-image is a separate property family from background-image, mask-border is a separate family from mask-image. It applies a mask only to the border-box edges (9-slice style):

.fancy-border {
  border: 20px solid transparent;
  mask-border-source: url('border-mask.svg');
  mask-border-slice: 33%;
  mask-border-width: 20px;
  mask-border-repeat: round;
  -webkit-mask-box-image-source: url('border-mask.svg');
  -webkit-mask-box-image-slice: 33%;
  -webkit-mask-box-image-width: 20px;
  -webkit-mask-box-image-repeat: round;
}
mask-border-* propertyEquivalent mask-* propertyPurpose
mask-border-sourcemask-imageURL or gradient
mask-border-slice(no parallel)How to cut the 9-slice
mask-border-width(no parallel)Width of each slice edge
mask-border-outset(no parallel)Offset from border box
mask-border-repeatmask-repeatround / repeat / stretch
mask-border-modemask-modealpha or luminance

Browser support: mask-border family is partial — Safari (with -webkit-mask-box-image-* prefix) has had it since 2009; Chrome shipped support in 120; Firefox is partial. Use @supports to detect.

The Complete -webkit- Prefix Checklist

For maximum browser support, include both prefixed and unprefixed versions:

.masked {
  -webkit-mask-image: linear-gradient(to bottom, black 60%, transparent);
  mask-image: linear-gradient(to bottom, black 60%, transparent);

  -webkit-mask-size: cover;
  mask-size: cover;

  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;

  -webkit-mask-position: center;
  mask-position: center;

  -webkit-mask-mode: alpha;
  mask-mode: alpha;

  /* DIFFERENT VALUE NAMES! */
  -webkit-mask-composite: source-over;
  mask-composite: add;
}

Browser Support

PropertyChromeFirefoxSafariEdge
mask (with gradients)120+53+15.4+ (unprefixed) / 4+ (prefixed)120+
mask-composite120+53+15.4+120+
mask-border-*120+partial4+ (prefixed)120+
animation-timeline: view() for scroll-driven mask115+behind flagnot yet115+

The -webkit- prefix covers Safari from version 4 (2009). Always include the prefix alongside the unprefixed version.

/* Fallback with @supports — no mask */
.el { /* default styles */ }

/* Enhanced with mask */
@supports (mask-image: linear-gradient(black, transparent)) or
          (-webkit-mask-image: linear-gradient(black, transparent)) {
  .el {
    -webkit-mask-image: linear-gradient(to bottom, black 60%, transparent);
    mask-image: linear-gradient(to bottom, black 60%, transparent);
  }
}

CSS mask not working safari? Debug Checklist

Nine times out of ten it’s a missing -webkit- prefix. The remaining bugs:

No visible mask effect:

  • Did you include -webkit-mask-image alongside mask-image? Safari requires the prefix
  • Is mask-image set to none somewhere in the cascade?
  • Check the gradient direction — to bottom fades from visible-at-top to transparent-at-bottom (in alpha mode)

mask-composite not working in Safari:

  • Are you using standard value names (add, subtract, intersect, exclude) for -webkit-mask-composite? Safari needs the different names: source-over, source-out, source-in, xor

mask-composite: intersect “does nothing”:

  • It only operates with 2+ mask layers. A single mask-image ignores composite
  • Layer order is right-to-left — the FIRST mask listed is the TOP layer

SVG mask looks wrong:

  • SVG <mask> uses luminance mode by default — black = hidden, white = visible
  • The SVG element must be in the DOM (not just an external file URL) for Firefox compatibility
  • If mask-type on the SVG and mask-mode on the CSS disagree, the CSS wins (unless mask-mode is match-source)

Mask disappears in print:

  • Print stylesheets generally strip blend modes and masks. Override with @media print { * { mask-image: none !important; } } or provide a clean fallback

Mask disappears in High Contrast Mode:

  • forced-colors: active (Windows High Contrast) strips mask effects. Provide a fallback with @media (forced-colors: active) that doesn’t rely on masking for accessibility-critical content

Key Takeaways

  • Forget “black = visible” as a universal rule — in alpha mode (default for gradients), only the alpha channel matters; color is ignored. In luminance mode (SVG <mask> default), brightness controls visibility
  • Use mask for soft fades and gradient reveals; use clip-path for sharp geometric shapes
  • SVG icon recoloring with mask-image: url(icon.svg) + background-color: currentColor is the #1 practical use of CSS mask in 2026
  • Fade scroll container edges with linear-gradient(to right, transparent, black 15%, black 85%, transparent) — the most popular pattern
  • Feather all 4 edges by stacking two linear gradients with mask-composite: intersect (-webkit-mask-composite: source-in)
  • Spotlight effects use a radial-gradient() mask with the cursor position updated via CSS custom properties — far cheaper than rewriting the full mask-image value
  • Animated wipe reveals work by animating gradient stop percentages — simpler than clip-path animation
  • Scroll-driven mask reveals using animation-timeline: view() are the 2026-defining pattern — zero JavaScript, ties mask animation to viewport entry
  • mask-mode: alpha (gradients default) uses transparency; mask-mode: luminance (SVG default) uses brightness
  • mask-type on the SVG <mask> element sets the default mode; CSS mask-mode overrides (unless mask-mode: match-source)
  • The parallel mask-border family applies masks only to the border-box edges (9-slice)
  • Critical: -webkit-mask-composite uses completely different value names than mask-compositesource-overadd in name only
  • mask-composite does nothing with a single mask layer — it only operates between 2+ layers
  • Always include both prefixed (-webkit-mask-*) and unprefixed versions of all mask properties

FAQ

What is the CSS mask property?

The CSS mask property (shorthand for mask-image and related properties) controls element visibility by using another image or gradient as a stencil. In alpha mode (default for gradients), the alpha channel of each mask pixel controls visibility — opaque = visible, transparent = hidden. In luminance mode (default for SVG <mask>), brightness controls visibility — white = visible, black = hidden. Unlike clip-path which creates hard binary edges, mask enables soft fades and partial transparency.

What is the difference between CSS mask and clip-path?

clip-path clips an element to a geometric shape — everything inside is fully visible, everything outside is completely hidden, with a hard sharp edge. mask uses an image or gradient to control visibility with soft fades and partial transparency. Use clip-path for sharp geometric shapes and mask for fades, spotlights, gradient reveals, or any effect requiring soft or graduated edges.

Why is my CSS mask not working in Safari?

Nine times out of ten it’s a missing -webkit- prefix. Always include -webkit-mask-image alongside mask-image. Also check mask-composite — Safari’s -webkit-mask-composite uses different value names: source-over (not add), source-out (not subtract), source-in (not intersect), xor (not exclude). If you wrote -webkit-mask-composite: subtract, Safari ignores it because that value doesn’t exist in the webkit vocabulary.

What is alpha vs luminance mask mode in CSS?

Alpha mode (default for CSS gradients) uses the transparency channel — opaque areas visible, transparent areas hidden, with color completely ignored. Luminance mode (default for SVG <mask> elements) uses pixel brightness — black = hidden, white = visible, grey = partial. Set mask-mode: luminance explicitly when working with SVG masks or greyscale images where brightness should control visibility instead of alpha.

Can I make masked icons follow my theme or dark mode?

Yes — that’s the most popular use of mask-image in 2026. Set mask-image: url(icon.svg) and background-color: currentColor on a div or <span>. The icon’s color is then whatever the surrounding text color is — change theme, hover, link color, and the icon follows. No SVG file duplication, no inline SVG bloat, no JavaScript. The limitation is that the source SVG must be single-color (any color in the file is replaced by the background-color).

How do I fade the edges of a scrollable container with CSS?

Apply a mask gradient to the scrollable container: mask-image: linear-gradient(to right, transparent, black 15%, black 85%, transparent). This fades the left and right 15% of the container to transparent while keeping the middle fully visible. Adjust the stop percentages to control fade distance. To fade all four edges (top + bottom + left + right), stack two gradients with mask-composite: intersect. Include -webkit-mask-image with the same value for Safari.

How do I animate a CSS mask reveal?

Use @keyframes to animate the gradient stop position. Start with mask-image: linear-gradient(to right, black 0%, transparent 0%) (hidden) and end with mask-image: linear-gradient(to right, black 100%, transparent 100%) (revealed). The browser interpolates the stop percentage for a smooth wipe. For scroll-tied reveals in 2026, use animation-timeline: view() instead of IntersectionObserver — the animation runs based on the element’s position in the viewport with zero JavaScript. Always add @media (prefers-reduced-motion: reduce) to disable for users with motion sensitivity.

Why does mask-composite: intersect look like nothing happened?

Two likely causes. First: mask-composite only operates between 2+ mask layers — a single mask-image ignores composite entirely. Make sure your mask-image value has at least two comma-separated layers. Second: in Safari, -webkit-mask-composite: intersect is wrong; the webkit value is source-in. If both standard and prefixed values are set to intersect, Safari silently does nothing. Use -webkit-mask-composite: source-in alongside mask-composite: intersect.