What Is CSS Masking
CSS masking lets you control which parts of an element are visible using an “alpha map”. Wherever the mask is opaque, your element shows. Wherever the mask is transparent, your element disappears.
- Think of it like a stencil: the stencil decides what shows through.
- Masking can be done with an image (PNG/SVG) or a gradient.
- Masking is different from clipping: clipping is usually hard edges (like a cookie cutter), masks can be soft, blurry, feathered, animated… all the fun stuff.
One important reality check: Older versions of Safari and Chrome still expects the -webkit- prefixed
versions for many mask
properties,
so in this tutorial we’ll include both.
The mask Property Shorthand
CSS gives you a shorthand mask property, plus longhand properties like
mask-image, mask-size, mask-position, mask-repeat, and more.
The shorthand is convenient once you know what’s being set.
.card {
mask: radial-gradient(circle at 30% 30%, #000 55%, transparent 60%);
-webkit-mask: radial-gradient(circle at 30% 30%, #000 55%, transparent 60%);
}
.card {
mask-image: radial-gradient(circle at 30% 30%, #000 55%, transparent 60%);
-webkit-mask-image: radial-gradient(circle at 30% 30%, #000 55%, transparent 60%);
}
.card {
mask-image: radial-gradient(circle at 30% 30%, #000 55%, transparent 60%);
-webkit-mask-image: radial-gradient(circle at 30% 30%, #000 55%, transparent 60%);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: 130% 130%;
-webkit-mask-size: 130% 130%;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.card {
width: min(640px, 100%);
padding: 22px;
border: 3px solid #111;
border-radius: 18px;
background-image: url("https://picsum.photos/1200/800");
background-size: cover;
background-position: center;
color: #111;
}
.card__inner {
background: rgba(255, 255, 255, 0.86);
border: 2px solid #111;
border-radius: 14px;
padding: 16px;
}
.card__title {
margin: 0 0 8px;
font-size: 20px;
}
.card__text {
margin: 0;
line-height: 1.45;
font-size: 15px;
}
Mask basics
Switch snippets: the first uses the
maskshorthand, the second sets onlymask-image, and the third addsmask-repeat+mask-size.
Notice how Snippet 1 and 2 give the same result, yet are written differently.
Also notice how the mask is applied to the entire element (including its background and content). That’s usually what you want, but later we’ll also do “masking text” as a special case.
CSS mask-image
mask-image is the star of the show. It defines the mask source:
-
A URL:
mask-image: url(...)(PNG or SVG are common). -
A gradient:
mask-image: linear-gradient(...),radial-gradient(...), etc. - Multiple masks: comma-separated (advanced, but very powerful).
mask-image With a Real Image
In this playground, the element has a photo background, and we use an SVG file as the mask. That means the photo only shows where the SVG is “opaque”.
.logo-mask {
mask-image: url("https://interactivecss.com/wp-content/uploads/2026/02/IonLogoCss3.svg");
-webkit-mask-image: url("https://interactivecss.com/wp-content/uploads/2026/02/IonLogoCss3.svg");
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-size: contain;
mask-position: center;
-webkit-mask-position: center;
}
.logo-mask {
mask-image: url("https://interactivecss.com/wp-content/uploads/2026/02/IonLogoCss3.svg");
-webkit-mask-image: url("https://interactivecss.com/wp-content/uploads/2026/02/IonLogoCss3.svg");
mask-repeat: repeat;
-webkit-mask-repeat: repeat;
mask-size: 140px 140px;
-webkit-mask-size: 140px 140px;
}
.logo-mask {
mask-image: url("https://interactivecss.com/wp-content/uploads/2026/02/IonLogoCss3.svg");
-webkit-mask-image: url("https://interactivecss.com/wp-content/uploads/2026/02/IonLogoCss3.svg");
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: 80% 80%;
-webkit-mask-size: 80% 80%;
mask-position: 20% 50%;
-webkit-mask-position: 20% 50%;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.logo-mask {
width: min(720px, 100%);
aspect-ratio: 16 / 9;
border: 3px solid #111;
border-radius: 18px;
background-image: url("https://picsum.photos/1400/900");
background-size: cover;
background-position: center;
}
.note {
margin: 12px 0 0;
font-size: 14px;
line-height: 1.45;
opacity: 0.85;
}
The SVG is used as a mask. Try switching snippets to see
mask-repeat,mask-size, andmask-positionbehavior.
Beginners’ gotcha: if your SVG mask looks “invisible”, it may be because the SVG’s shapes aren’t producing alpha the way you expect. When possible, use a clean SVG with filled paths for predictable results.
mask-image With Gradients
Gradients are the easiest way to create soft fades, spotlights, and vignette effects. Anywhere the gradient is transparent, the element disappears.
.panel {
mask-image: linear-gradient(to right, transparent, #000 20%, #000 80%, transparent);
-webkit-mask-image: linear-gradient(to right, transparent, #000 20%, #000 80%, transparent);
}
.panel {
mask-image: radial-gradient(circle at 30% 40%, #000 45%, transparent 62%);
-webkit-mask-image: radial-gradient(circle at 30% 40%, #000 45%, transparent 62%);
}
.panel {
mask-image: repeating-linear-gradient(
45deg,
#000 0 18px,
transparent 18px 30px
);
-webkit-mask-image: repeating-linear-gradient(
45deg,
#000 0 18px,
transparent 18px 30px
);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.panel {
width: min(720px, 100%);
height: 260px;
border: 3px solid #111;
border-radius: 18px;
background-image: url("https://picsum.photos/1300/900");
background-size: cover;
background-position: center;
}
.tip {
margin: 12px 0 0;
font-size: 14px;
line-height: 1.45;
opacity: 0.85;
}
Gradients make great masks: fades, spotlights, and patterns.
Learn more about gradients in the CSS Gradient Interactive Tutorial.
CSS mask-size
mask-size controls how large the mask image is, just like background-size.
Common values:
-
containkeeps the whole mask visible (no cropping). -
coverfills the element (cropping is likely). -
Specific sizes like
120px 120pxor70% 70%.
This playground uses sliders to resize a mask.
.stage {
mask-size: 70% 70%;
mask-image: url("https://interactivecss.com/wp-content/uploads/2026/02/IonLogoCss3.svg");
-webkit-mask-image: url("https://interactivecss.com/wp-content/uploads/2026/02/IonLogoCss3.svg");
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-position: center;
-webkit-mask-position: center;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.stage {
width: min(720px, 100%);
height: 280px;
border: 3px solid #111;
border-radius: 18px;
background-image: url("https://picsum.photos/1400/900");
background-size: cover;
background-position: center;
}
.hint {
margin: 12px 0 0;
font-size: 14px;
line-height: 1.45;
opacity: 0.85;
}
The sliders replace the two values in
mask-size: 70% 70%;(width then height).
CSS mask-position
mask-position moves the mask around, just like background-position.
You can use keywords (center, top, left) or percentages.
.stage {
mask-position: 50% 50%;
mask-image: radial-gradient(circle, #000 45%, transparent 60%);
-webkit-mask-image: radial-gradient(circle, #000 45%, transparent 60%);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: 50% 70%;
-webkit-mask-size: 50% 70%;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.stage {
width: min(720px, 100%);
height: 280px;
border: 3px solid #111;
border-radius: 18px;
background-image: url("https://picsum.photos/1400/900");
background-size: cover;
background-position: center;
}
.hint {
margin: 12px 0 0;
font-size: 14px;
line-height: 1.45;
opacity: 0.85;
}
You’re moving a “spotlight” mask. This is a classic technique for hover reveals and focus effects.
CSS mask-repeat
mask-repeat decides whether the mask tiles. If you’re using a single logo-like mask,
you almost always want no-repeat. If you want a pattern, repeating is perfect.
.pattern {
mask-image: url("https://interactivecss.com/wp-content/uploads/2026/02/IonLogoCss3.svg");
-webkit-mask-image: url("https://interactivecss.com/wp-content/uploads/2026/02/IonLogoCss3.svg");
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: 220px 220px;
-webkit-mask-size: 220px 220px;
mask-position: center;
-webkit-mask-position: center;
}
.pattern {
mask-image: url("https://interactivecss.com/wp-content/uploads/2026/02/IonLogoCss3.svg");
-webkit-mask-image: url("https://interactivecss.com/wp-content/uploads/2026/02/IonLogoCss3.svg");
mask-repeat: repeat;
-webkit-mask-repeat: repeat;
mask-size: 140px 140px;
-webkit-mask-size: 140px 140px;
}
.pattern {
mask-image: repeating-linear-gradient(
90deg,
#000 0 14px,
transparent 14px 24px
);
-webkit-mask-image: repeating-linear-gradient(
90deg,
#000 0 14px,
transparent 14px 24px
);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.pattern {
width: min(720px, 100%);
height: 260px;
border: 3px solid #111;
border-radius: 18px;
background-image: url("https://picsum.photos/1400/900");
background-size: cover;
background-position: center;
}
.note {
margin: 12px 0 0;
font-size: 14px;
line-height: 1.45;
opacity: 0.85;
}
Masks can tile. Great for patterns, “scanline” effects, and texture reveals.
CSS mask-clip and mask-origin
These two properties are where masking starts to feel like background painting. They control which box the mask is positioned from and clipped to.
-
mask-originsets the reference box for positioning and sizing (oftenborder-boxorcontent-box). -
mask-clipsets the box that clips the mask painting (so the mask can be limited to the border/padding/content area).
This is especially useful when you have thick borders and padding and you want the mask to affect only a certain area.
.card {
mask-image: radial-gradient(circle, #000 55%, transparent 60%);
-webkit-mask-image: radial-gradient(circle, #000 55%, transparent 60%);
mask-origin: border-box;
-webkit-mask-origin: border-box;
mask-clip: border-box;
-webkit-mask-clip: border-box;
}
.card {
mask-image: radial-gradient(circle, #000 55%, transparent 60%);
-webkit-mask-image: radial-gradient(circle, #000 55%, transparent 60%);
mask-origin: padding-box;
-webkit-mask-origin: padding-box;
mask-clip: padding-box;
-webkit-mask-clip: padding-box;
}
.card {
mask-image: radial-gradient(circle, #000 55%, transparent 60%);
-webkit-mask-image: radial-gradient(circle, #000 55%, transparent 60%);
mask-origin: content-box;
-webkit-mask-origin: content-box;
mask-clip: content-box;
-webkit-mask-clip: content-box;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.card {
width: min(720px, 100%);
padding: 26px;
border: 12px solid #111;
border-radius: 22px;
background-image: url("https://picsum.photos/1400/900");
background-size: cover;
background-position: center;
}
.inner {
border-radius: 14px;
border: 2px solid #111;
background: rgba(255, 255, 255, 0.88);
padding: 14px;
}
.inner p {
margin: 0;
line-height: 1.45;
}
Switch snippets to change
mask-originandmask-clip. Watch how the mask behaves relative to the thick border and padding.
Tip: if you’re not seeing a difference, make the element’s border thicker and add padding. These properties are all about the element’s boxes.
CSS Mask Clip in Practice
People often search for “mask clip” because they want the mask to affect only a portion of a box. Here’s a practical demo: a “window” cut into a thick framed panel.
.frame {
mask-image: radial-gradient(circle, transparent 0 54%, #000 57%);
-webkit-mask-image: radial-gradient(circle, transparent 0 54%, #000 57%);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: 240px 240px;
-webkit-mask-size: 240px 240px;
mask-position: center;
-webkit-mask-position: center;
mask-clip: padding-box;
-webkit-mask-clip: padding-box;
}
.frame {
mask-image: radial-gradient(circle, transparent 0 54%, #000 57%);
-webkit-mask-image: radial-gradient(circle, transparent 0 54%, #000 57%);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: 240px 240px;
-webkit-mask-size: 240px 240px;
mask-position: center;
-webkit-mask-position: center;
mask-clip: content-box;
-webkit-mask-clip: content-box;
}
.frame {
mask-image: radial-gradient(circle, transparent 0 54%, #000 57%);
-webkit-mask-image: radial-gradient(circle, transparent 0 54%, #000 57%);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: 240px 240px;
-webkit-mask-size: 240px 240px;
mask-position: center;
-webkit-mask-position: center;
mask-clip: border-box;
-webkit-mask-clip: border-box;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.frame {
width: min(720px, 100%);
height: 280px;
padding: 26px;
border: 16px solid #111;
border-radius: 22px;
background-image: url("https://picsum.photos/1400/900");
background-size: cover;
background-position: center;
}
.frame__inner {
height: 100%;
border-radius: 14px;
border: 2px solid #111;
background: rgba(255, 255, 255, 0.88);
display: grid;
place-items: center;
text-align: center;
padding: 12px;
}
.frame__inner p {
margin: 0;
line-height: 1.45;
max-width: 48ch;
}
Switch snippets to change
mask-clip. You’re controlling which box “contains” the mask paint.
CSS Mask Text
“Masking text” can mean different things. Two very common beginner-friendly meanings are:
- Fade text out (use a gradient mask on the element that contains text).
- Reveal text with a moving mask (animated gradient mask).
- Clipping the background to the text (use a mask to show the background only where the text is).
Fade Out Text With a Gradient Mask
This is a clean way to make a paragraph fade at the bottom without extra HTML. The mask affects the element (and therefore its text).
.fade {
mask-image: linear-gradient(to bottom, #000 0 70%, transparent);
-webkit-mask-image: linear-gradient(to bottom, #000 0 70%, transparent);
}
.fade {
mask-image: linear-gradient(to bottom, #000 0 45%, transparent);
-webkit-mask-image: linear-gradient(to bottom, #000 0 45%, transparent);
}
.fade {
mask-image: radial-gradient(circle at 50% 0%, #000 55%, transparent 85%);
-webkit-mask-image: radial-gradient(circle at 50% 0%, #000 55%, transparent 85%);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.fade {
width: min(720px, 100%);
border: 3px solid #111;
border-radius: 18px;
background: #fff;
padding: 16px;
line-height: 1.55;
}
.fade p {
margin: 0 0 10px;
}
.fade p:last-child {
margin: 0;
}
This block is being masked. The text fades away where the mask becomes transparent. It’s a neat trick for “scroll hint” areas, previews, and cut-off content.
Switch snippets: different gradients produce different fade vibes. Soft fade, harder fade, or a spotlight-style fade.
Extra: because it’s a mask, you can animate it (next section).
Animated Text Reveal Mask
Here we animate a repeating gradient mask to create a “scanner reveal” effect over text.
@keyframes scan {
to {
mask-position: 220px 0;
-webkit-mask-position: 220px 0;
}
}
.scan {
mask-image: repeating-linear-gradient(
90deg,
transparent 0 10px,
#000 10px 26px
);
-webkit-mask-image: repeating-linear-gradient(
90deg,
transparent 0 10px,
#000 10px 26px
);
mask-size: 220px 100%;
-webkit-mask-size: 220px 100%;
animation: scan 1.2s linear infinite;
}
@keyframes scan {
to {
mask-position: 0 160px;
-webkit-mask-position: 0 160px;
}
}
.scan {
mask-image: repeating-linear-gradient(
0deg,
transparent 0 10px,
#000 10px 26px
);
-webkit-mask-image: repeating-linear-gradient(
0deg,
transparent 0 10px,
#000 10px 26px
);
mask-size: 100% 160px;
-webkit-mask-size: 100% 160px;
animation: scan 1.2s linear infinite;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.scan {
width: min(720px, 100%);
border: 3px solid #111;
border-radius: 18px;
background: #fff;
padding: 18px;
line-height: 1.5;
}
.scan__title {
margin: 0 0 10px;
font-size: 20px;
}
.scan__text {
margin: 0;
font-size: 15px;
opacity: 0.92;
}
Masked text “scanner”
The mask is moving, not the text. Switch snippets to scan horizontally or vertically. This is pure CSS: mask image + mask size + mask position animation.
Clipping the Background to the Text
If what you are looking for is showing the background only where the text is, you can use the
background-clip: text; property to achieve this effect.
This is not technically masking, but it’s often what people mean by “mask text”: the background becomes visible only inside the letter shapes. The key is that the text color becomes transparent, and the background is clipped to the text.
-
You’ll also need
-webkit-background-clip: text;for older browsers. -
Most of the time you’ll also set
-webkit-text-fill-color: transparent;to ensure the fill is transparent in WebKit browsers.
.headline {
background-image: url("https://picsum.photos/1400/900");
background-size: cover;
background-position: center;
color: transparent;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.headline {
background-image: linear-gradient(90deg, #0b5fff, #ff2d6d, #ffb300);
background-size: 200% 100%;
background-position: 0% 50%;
color: transparent;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
@keyframes slide {
to {
background-position: 100% 50%;
}
}
.headline {
background-image: linear-gradient(90deg, #0b5fff, #ff2d6d, #ffb300);
background-size: 220% 100%;
color: transparent;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: slide 2.2s linear infinite;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.card {
width: min(860px, 100%);
border: 3px solid #111;
border-radius: 18px;
background: #fff;
padding: 18px;
}
.headline {
margin: 0;
font-weight: 800;
letter-spacing: -0.02em;
line-height: 1.05;
font-size: clamp(40px, 6vw, 76px);
}
.sub {
margin: 12px 0 0;
font-size: 14px;
line-height: 1.45;
opacity: 0.85;
}
Background Inside Text
Switch snippets: a gradient fill, an image fill, and an animated gradient. This uses
background-clip: text(notmask-image), but it’s a very common “masked text” effect.
Browser Support and the -webkit- Prefix
CSS masking is widely supported in modern browsers, but there’s a big practical catch: older versions of Safari and Chrome still expect WebKit-prefixed mask properties.
That’s why, throughout this tutorial, you’ll often see both the unprefixed and prefixed versions:
mask-* and -webkit-mask-*.
In practice, the safest pattern is to include the unprefixed property first, then the prefixed one right after it.
.box {
mask-image: radial-gradient(circle, #000 55%, transparent 62%);
-webkit-mask-image: radial-gradient(circle, #000 55%, transparent 62%);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-position: center;
-webkit-mask-position: center;
mask-size: 120% 120%;
-webkit-mask-size: 120% 120%;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.box {
width: min(720px, 100%);
height: 260px;
border: 3px solid #111;
border-radius: 18px;
background-image: url("https://picsum.photos/1400/900");
background-size: cover;
background-position: center;
}
.note {
margin: 12px 0 0;
font-size: 14px;
line-height: 1.45;
opacity: 0.85;
}
This snippet includes both unprefixed and
-webkit-prefixed mask properties for cross-browser reliability.
For the latest details by browser and feature, check Can I use.
Practical Tips and Troubleshooting
-
Include the prefix for older browsers:
many real-world projects still use both
mask-*and-webkit-mask-*. - Start simple: try a basic gradient mask first. If that works, then switch to a URL mask.
-
If the element disappears:
your mask might be fully transparent (easy to do with gradients).
Temporarily use
mask-image: linear-gradient(#000, #000);to confirm. -
If a URL mask “does nothing”:
verify the URL loads, and try
mask-repeat: no-repeat;plus an obviousmask-sizelike200px 200px. - Remember: masks affect everything inside the element: background, border, and content. If you only want to affect a background image, consider putting the background on a child element or a pseudo-element and masking that instead.
You now have the core masking toolkit: mask-image, gradients, SVG masks,
sizing, positioning, clipping, and text-friendly masking patterns.
From here, you can build reveals, soft fades, pattern cutouts, and fancy UI effects without extra markup.
