Understanding CSS Masks
The CSS mask property (and its related longhand properties) allows you to hide portions of an element by overlaying another image, gradient, or SVG element on top of it. Think of it as the opposite of clipping — while clip-path trims everything outside a defined shape, masking uses the alpha channel and luminance values of a mask layer to determine which parts of the element remain visible. Where the mask is fully opaque, the element shows through completely; where the mask is transparent, the element is hidden; and semi-transparent regions create partial visibility.
Mask vs. Clip-Path: The Critical Difference
Many developers confuse masking with clipping. The key distinction: clip-path works with hard vector edges — you either see a pixel or you don't. Masks, on the other hand, support soft edges, gradients, and complex alpha blending. This means you can create feathered transitions, vignettes, or intricate reveal effects that would be impossible with clipping alone.
Core Mask Properties
CSS masks mirror the background property syntax almost exactly. The full shorthand is mask, but you can control individual aspects using these longhand properties:
mask-image— The image, gradient, or SVG element used as the mask sourcemask-mode— Whether to use alpha (alpha) or luminance (luminance) values from the mask sourcemask-repeat— How the mask image tiles (repeat,no-repeat,repeat-x,repeat-y)mask-position— Where the mask image is placed within the elementmask-size— Dimensions of the mask image (cover,contain, or explicit values)mask-composite— How multiple mask layers blend together (add,subtract,intersect,exclude)mask-origin— The origin box for positioning the mask (border-box,padding-box,content-box)mask-clip— The area the mask applies to (border-box,padding-box,content-box,no-clip)
Why CSS Masks Matter
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Before CSS masks gained broad browser support, developers relied on hacks — wrapping elements in extra divs with overflow: hidden, using complex SVG clipping paths, or resorting to canvas manipulation. Masks bring several compelling advantages:
- Performance: Masking is hardware-accelerated in modern browsers, making it far more efficient than JavaScript-based pixel manipulation
- Declarative simplicity: Complex visual effects become a single CSS rule rather than dozens of lines of imperative code
- Responsiveness: Masks scale naturally with element dimensions, unlike fixed-size image overlays
- Composability: Multiple mask layers can be combined and blended directly in CSS
- Animation-ready: Mask properties can be animated with CSS transitions and keyframes for stunning reveal effects
Practical Examples
Basic Gradient Mask
The simplest and most common use case: fading out an image or element from one edge using a linear gradient as the mask.
<style>
.hero-image {
width: 100%;
height: 400px;
background-image: url('landscape.jpg');
background-size: cover;
background-position: center;
/* Fade the bottom edge to transparency */
-webkit-mask-image: linear-gradient(to bottom,
black 0%,
black 60%,
transparent 100%);
mask-image: linear-gradient(to bottom,
black 0%,
black 60%,
transparent 100%);
}
</style>
<div class="hero-image"></div>
This creates a smooth fade-out at the bottom 40% of the hero section. The black stops represent fully opaque mask regions, while transparent hides the element completely. Always include the -webkit-mask- prefixed version for broader browser compatibility, especially on older Safari versions.
Radial Gradient for Vignette Effects
Radial gradients are perfect for creating vignette-style reveals or circular spotlight effects.
<style>
.vignette-card {
width: 300px;
height: 400px;
background-image: url('portrait.jpg');
background-size: cover;
border-radius: 12px;
/* Create a soft circular mask that fades at edges */
-webkit-mask-image: radial-gradient(
circle at center,
black 40%,
transparent 70%
);
mask-image: radial-gradient(
circle at center,
black 40%,
transparent 70%
);
/* Ensure mask fits the element */
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
}
</style>
<div class="vignette-card"></div>
Using SVG as a Mask Source
SVG masks offer the most precision and flexibility. You can reference an SVG <mask> element or use any SVG shape directly as a mask-image. This approach is ideal for complex shapes, text masks, or when you need exact control over feathering via SVG's <mask> element with <filter> blur.
<style>
.svg-masked-element {
width: 400px;
height: 300px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
/* Reference an inline SVG mask by its ID */
-webkit-mask-image: url(#custom-mask);
mask-image: url(#custom-mask);
-webkit-mask-size: contain;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-position: center;
}
</style>
<svg width="0" height="0" style="position: absolute;">
<defs>
<mask id="custom-mask">
<rect width="400" height="300" fill="white" />
<circle cx="200" cy="150" r="80" fill="black" />
<text x="200" y="155"
text-anchor="middle"
font-size="28"
font-weight="bold"
fill="white">REVEAL</text>
</mask>
</defs>
</svg>
<div class="svg-masked-element"></div>
In this example, the gradient background shows through only where the SVG mask is white. The black circle creates a circular cutout, and the white text allows the background to show through the letterforms. You can also use an SVG shape directly as a mask-image by encoding it as a data URI.
Masking with External Images
Any image format can serve as a mask source. The alpha channel of PNG images works beautifully, while JPEG images use luminance values for masking.
<style>
.textured-mask {
width: 500px;
height: 350px;
background: url('colorful-scene.jpg') center/cover no-repeat;
/* Use a grayscale texture PNG's alpha channel as mask */
-webkit-mask-image: url('grunge-texture.png');
mask-image: url('grunge-texture.png');
-webkit-mask-mode: alpha;
mask-mode: alpha;
-webkit-mask-size: cover;
mask-size: cover;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
}
</style>
<div class="textured-mask"></div>
Combining Multiple Mask Layers
Like backgrounds, masks support multiple comma-separated layers. Each layer is processed independently, and the results are composited using the mask-composite mode. This is powerful for creating complex reveal patterns.
<style>
.multi-mask {
width: 400px;
height: 400px;
background: url('abstract-art.jpg') center/cover no-repeat;
/* Three mask layers combined */
-webkit-mask-image:
radial-gradient(circle at 30% 50%, black 20%, transparent 50%),
radial-gradient(circle at 70% 50%, black 20%, transparent 50%),
linear-gradient(to bottom, black 0%, black 80%, transparent 100%);
mask-image:
radial-gradient(circle at 30% 50%, black 20%, transparent 50%),
radial-gradient(circle at 70% 50%, black 20%, transparent 50%),
linear-gradient(to bottom, black 0%, black 80%, transparent 100%);
/* Add layers together (default composite mode) */
-webkit-mask-composite: add, add;
mask-composite: add, add;
-webkit-mask-size: 100% 100%, 100% 100%, 100% 100%;
mask-size: 100% 100%, 100% 100%, 100% 100%;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
}
</style>
<div class="multi-mask"></div>
Each mask layer is declared with commas separating the values. The mask-composite property controls how they blend. Available modes are:
add— Union of all mask layers (where any layer is opaque, the element shows)subtract— The top layer subtracts from the layers below itintersect— Only regions where all layers are opaque remain visibleexclude— Regions where an odd number of layers are opaque
Animating Masks with CSS
Mask position and size can be animated, creating dynamic reveal effects. This example creates a sweeping unveil animation on hover.
<style>
.reveal-card {
width: 300px;
height: 400px;
position: relative;
background-image: url('hidden-image.jpg');
background-size: cover;
border-radius: 8px;
/* Mask starts as a thin vertical slice */
-webkit-mask-image: linear-gradient(to right,
black 0%,
black 10%,
transparent 10%,
transparent 100%);
mask-image: linear-gradient(to right,
black 0%,
black 10%,
transparent 10%,
transparent 100%);
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
-webkit-mask-position: 0% 0%;
mask-position: 0% 0%;
transition: mask-position 0.6s cubic-bezier(0.4, 0, 0.2, 1),
-webkit-mask-position 0.6s cubic-bezier(0.4, 0, 0.2, 1);
}
.reveal-card:hover {
/* Slide mask to reveal the full image */
-webkit-mask-position: 100% 0%;
mask-position: 100% 0%;
}
</style>
<div class="reveal-card"></div>
For more complex animations, you can use @keyframes to animate multiple mask properties simultaneously.
<style>
@keyframes mask-reveal {
0% {
mask-size: 0% 100%;
-webkit-mask-size: 0% 100%;
mask-position: 0% 0%;
-webkit-mask-position: 0% 0%;
}
50% {
mask-size: 100% 100%;
-webkit-mask-size: 100% 100%;
mask-position: 0% 0%;
-webkit-mask-position: 0% 0%;
}
100% {
mask-size: 100% 100%;
-webkit-mask-size: 100% 100%;
mask-position: 100% 0%;
-webkit-mask-position: 100% 0%;
}
}
.animated-reveal {
width: 400px;
height: 300px;
background: url('panorama.jpg') center/cover no-repeat;
/* Gradient mask with sharp edge for wipe effect */
mask-image: linear-gradient(to right,
black 0%, black 50%, transparent 50%, transparent 100%);
-webkit-mask-image: linear-gradient(to right,
black 0%, black 50%, transparent 50%, transparent 100%);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
animation: mask-reveal 2s ease-out forwards;
}
</style>
<div class="animated-reveal"></div>
Best Practices and Tips
1. Always Include WebKit Prefixes
As of 2024, Safari still requires -webkit-mask- prefixes for many mask properties. Always duplicate your mask declarations with the prefixed version. A clean approach is to define both sets:
.element {
/* Standard property */
mask-image: linear-gradient(to bottom, black, transparent);
/* WebKit fallback */
-webkit-mask-image: linear-gradient(to bottom, black, transparent);
}
For shorthand mask, Safari's prefixed version behaves differently — it only accepts -webkit-mask-image values. Stick to longhand properties when you need reliable cross-browser behavior.
2. Understand mask-mode: alpha vs. luminance
By default, mask-mode is set to alpha (or match-source which defers to the source's native mode). This means the transparency channel of your mask image determines visibility. For SVG masks, luminance is the default. If you want consistent luminance-based masking across all sources, explicitly set mask-mode: luminance. This is particularly useful when using JPEG images or CSS gradients, where "black" means hidden and "white" means visible.
.luminance-mask {
mask-image: url('texture.jpg');
mask-mode: luminance;
-webkit-mask-mode: luminance;
}
3. Leverage mask-size and mask-position for Responsive Designs
Unlike fixed-dimension image overlays, masks scale with your element when you use relative values like cover or percentage-based sizes. This makes them inherently responsive. Combine mask-size: cover with mask-position: center for masks that adapt to any container dimensions.
.responsive-mask {
mask-image: url('organic-shape.svg');
mask-size: cover;
mask-position: center;
mask-repeat: no-repeat;
-webkit-mask-image: url('organic-shape.svg');
-webkit-mask-size: cover;
-webkit-mask-position: center;
-webkit-mask-repeat: no-repeat;
}
4. Use mask-origin and mask-clip for Precise Control
When your element has padding or borders, mask-origin determines where the mask positioning starts, and mask-clip controls where the mask actually applies. For instance, if you want the mask to cover only the content area while leaving borders unmasked, set mask-clip: content-box.
.bordered-box {
border: 8px solid rgba(0,0,0,0.2);
padding: 20px;
mask-image: radial-gradient(circle, black 30%, transparent 80%);
mask-origin: padding-box; /* Position relative to padding edge */
mask-clip: content-box; /* Apply mask only within content area */
-webkit-mask-image: radial-gradient(circle, black 30%, transparent 80%);
-webkit-mask-origin: padding-box;
-webkit-mask-clip: content-box;
}
5. Optimize Mask Performance
While masks are GPU-accelerated, certain practices keep rendering fast:
- Prefer CSS gradients over external images when possible — gradients are computed on the GPU and don't require additional HTTP requests
- Keep SVG masks simple — complex SVG filters (like
<feGaussianBlur>within masks) can trigger expensive software rendering paths - Avoid animating mask-image itself — instead animate
mask-position,mask-size, ormask-composite, which are cheaper to interpolate - Use
will-changesparingly — only apply it to elements that will actually animate, and remove it when animation ends
.performant-mask {
/* Gradient masks are GPU-friendly */
mask-image: radial-gradient(circle at 50% 50%, black 0%, transparent 70%);
/* Animate position, not the image */
transition: mask-position 0.3s ease;
/* Hint the browser only if animation is imminent */
will-change: mask-position;
}
6. Master mask-composite for Layered Effects
When using multiple mask layers, mask-composite is your most powerful tool. The default add mode creates a union of all layers. Use intersect to create "window" effects where only overlapping mask regions reveal the element. Use subtract to carve holes.
.intersection-mask {
/* Only the intersection of these two circles reveals the image */
mask-image:
radial-gradient(circle at 30% 50%, black 40%, transparent 60%),
radial-gradient(circle at 70% 50%, black 40%, transparent 60%);
mask-composite: intersect;
mask-size: 100% 100%, 100% 100%;
mask-repeat: no-repeat;
-webkit-mask-image:
radial-gradient(circle at 30% 50%, black 40%, transparent 60%),
radial-gradient(circle at 70% 50%, black 40%, transparent 60%);
-webkit-mask-composite: source-in; /* Safari uses different keywords */
-webkit-mask-size: 100% 100%, 100% 100%;
-webkit-mask-repeat: no-repeat;
}
Note that Safari's -webkit-mask-composite uses non-standard keywords: source-over (add), source-in (intersect), source-out (subtract), and source-atop. Always test across browsers when using composite operations.
7. Test with Fallbacks
Not all browsers support CSS masks (though coverage now exceeds 95%). Provide a sensible fallback by placing your mask declarations inside a @supports block:
.masked-element {
/* Fallback: show the full background */
background: url('hero.jpg') center/cover no-repeat;
}
@supports (mask-image: linear-gradient(black, transparent)) or
(-webkit-mask-image: linear-gradient(black, transparent)) {
.masked-element {
mask-image: linear-gradient(to bottom, black 70%, transparent 100%);
-webkit-mask-image: linear-gradient(to bottom, black 70%, transparent 100%);
}
}
8. Debugging Mask Issues
When a mask doesn't work as expected, check these common pitfalls:
- Missing size declaration: Without
mask-size, gradient masks default toauto, which may collapse to zero - Mask origin confusion: The mask might be positioned relative to the wrong box model edge
- SVG mask ID scoping: Inline SVG masks referenced via
url(#id)must be in the same document - WebKit prefix mismatch: Safari's shorthand
-webkit-maskdoesn't accept the same values as the standardmaskshorthand — always use longhand prefixed properties - Stacking context: Applying a mask creates a new stacking context, which can affect z-index behavior
Real-World Use Cases
Image Gallery with Hover Reveal
<style>
.gallery-item {
width: 250px;
height: 300px;
background: url('thumbnail.jpg') center/cover no-repeat;
border-radius: 16px;
cursor: pointer;
overflow: hidden;
/* Start fully hidden, reveal on hover */
mask-image: linear-gradient(to top, transparent 0%, black 0%);
mask-size: 100% 200%;
mask-position: 0% 100%;
mask-repeat: no-repeat;
-webkit-mask-image: linear-gradient(to top, transparent 0%, black 0%);
-webkit-mask-size: 100% 200%;
-webkit-mask-position: 0% 100%;
-webkit-mask-repeat: no-repeat;
transition: mask-position 0.5s ease-out,
-webkit-mask-position 0.5s ease-out;
}
.gallery-item:hover {
mask-position: 0% 0%;
-webkit-mask-position: 0% 0%;
}
</style>
<div class="gallery-item"></div>
Text Gradient with Mask
Masking text with a gradient creates stunning typographic effects that go beyond what background-clip: text can achieve.
<style>
.gradient-headline {
font-size: 72px;
font-weight: 900;
color: transparent;
background: linear-gradient(45deg, #f09433, #e6683c, #dc2743, #cc2366);
background-clip: text;
-webkit-background-clip: text;
/* Add a shimmer animation using mask */
position: relative;
}
.gradient-headline::after {
content: attr(data-text);
position: absolute;
inset: 0;
color: white;
background: none;
/* Diagonal shimmer mask */
mask-image: linear-gradient(
105deg,
transparent 0%,
transparent 40%,
black 45%,
transparent 50%,
transparent 100%
);
mask-size: 200% 100%;
mask-position: 200% 0%;
-webkit-mask-image: linear-gradient(
105deg,
transparent 0%,
transparent 40%,
black 45%,
transparent 50%,
transparent 100%
);
-webkit-mask-size: 200% 100%;
-webkit-mask-position: 200% 0%;
animation: shimmer 3s infinite linear;
}
@keyframes shimmer {
0% {
mask-position: 200% 0%;
-webkit-mask-position: 200% 0%;
}
100% {
mask-position: -100% 0%;
-webkit-mask-position: -100% 0%;
}
}
</style>
<h1 class="gradient-headline" data-text="Mastering CSS Masks">
Mastering CSS Masks
</h1>
Irregular-Shaped Image Crops
Use an organic SVG shape as a mask to give images non-rectangular, creative boundaries.
<style>
.organic-crop {
width: 350px;
height: 450px;
background: url('model-shot.jpg') center/cover no-repeat;
/* Use an SVG blob shape as mask */
mask-image: url('data:image/svg+xml,
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<path d="M50,5 C75,10 95,30 90,55 C85,80 60,95 35,90
C10,85 5,65 10,40 C15,15 30,0 50,5Z"
fill="black"/>
</svg>');
mask-size: contain;
mask-repeat: no-repeat;
mask-position: center;
-webkit-mask-image: url('data:image/svg+xml,
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<path d="M50,5 C75,10 95,30 90,55 C85,80 60,95 35,90
C10,85 5,65 10,40 C15,15 30,0 50,5Z"
fill="black"/>
</svg>');
-webkit-mask-size: contain;
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
}
</style>
<div class="organic-crop"></div>
Browser Compatibility and Polyfills
CSS mask properties are supported in all modern browsers, including Chrome, Firefox, Safari, and Edge. However, Safari requires the -webkit- prefix for all mask properties. Internet Explorer never supported CSS masks. For projects requiring IE compatibility, consider using SVG <mask> elements directly on SVG content, or fall back to clip-path when soft edges aren't essential.
Always test on real devices. A robust approach is to design your UI so that masking enhances the experience but isn't critical for functionality — the element should still be usable and recognizable without the mask applied.
Conclusion
CSS masks unlock a dimension of visual design that was previously the domain of image editing software or complex JavaScript. By understanding the mask property family — from basic gradient fades to multi-layer composited effects — you gain precise, performant control over how elements reveal themselves on screen. Remember to always pair standard properties with WebKit-prefixed versions, prefer GPU-friendly gradient masks for performance, and use mask-composite thoughtfully to build layered effects. With these techniques in your toolkit, you can create immersive hero sections, animated card reveals, textured image treatments, and sophisticated typographic effects — all with a few lines of declarative CSS. Start experimenting with masks today, and you'll find they quickly become an indispensable part of your front-end craft.