CSS Masking
Optional Advanced just poured newspaper columns. This session is a different optional tool: masking. You put a stencil on one box. Where the stencil is solid, you see the photo or color. Where it is empty, that box disappears. You are not splitting an article into streams, and you are not sliding icon tiles on a sprite sheet.
Direct answer
CSS masking hides parts of an element with mask-image — a PNG, an SVG, or a CSS gradient. Think of spray paint through a cut-out. mask-size, mask-repeat, and mask-position work like the background layer: cover, no-repeat, center. mask-mode chooses alpha (transparency of the mask file) or luminance (light vs dark). Use a mask when the edge should fade. Use clip-path when you want a hard circle or polygon. Keep labels and buttons fully visible.
Why a mask is not a column or a sprite
Multi-column fragments one text stream. A sprite sheet crops one bitmap with background-position so many icons share a file. A mask does neither. The element still occupies its box. Layout width and height do not shrink. You only change which pixels of that box are painted. Filters (blur(), grayscale()) change the look of every visible pixel; a mask decides which pixels remain.
A PNG as the stencil
mask-image takes url(...) the same way background-image does. A typical mask PNG is white (or opaque) where you want the photo, and transparent where you want a hole:
.hero-photo {
width: 20rem;
height: 12rem;
background: url("/img/studio.jpg") center / cover no-repeat;
mask-image: url("/img/soft-blob.png");
mask-size: cover;
mask-repeat: no-repeat;
}
The photo is still a background (or an <img> with the same mask). The blob PNG is only the stencil. If the mask file is missing, the whole element can vanish — keep a fallback unmasked style, or test the URL.
Fade with a gradient
You do not need a second image file. A gradient mask is the everyday fade at the bottom of a card:
.card-lead {
mask-image: linear-gradient(to bottom, #000 60%, transparent);
mask-repeat: no-repeat;
}
Black (or fully opaque) keeps the content; transparent punches a hole. The gradient lives in CSS, so there is no extra HTTP request — unlike a sprite sheet of icons. This is still not opacity on the whole card: text in the solid part stays crisp; only the masked region dissolves.
Size, repeat, and position
Treat the mask like a background layer you never see as a picture:
.badge {
mask-image: url("/img/star-mask.svg");
mask-size: 4rem 4rem;
mask-repeat: no-repeat;
mask-position: center;
}
mask-size: cover fills the box; contain keeps the stencil’s aspect ratio; lengths pin a small shape. repeat tiles the stencil (rare, and easy to overdo). Shorthand mask exists, but beginners stay safer with the longhands until the layer feels like background.
Alpha vs luminance
mask-mode answers: “Do I read transparency, or brightness?”
.portrait {
mask-image: url("/img/gray-sculpt.jpg");
mask-mode: luminance;
mask-size: cover;
}
alpha (often the default for PNG with transparency) uses the mask’s alpha channel. luminance uses light vs dark: white typically shows, black typically hides — useful when your stencil is a gray photo, not a transparent PNG. If a JPEG mask “does nothing,” you probably needed luminance. There is also mask-type on SVG <mask> elements; in CSS on HTML boxes, start with mask-mode.
Hard clip vs soft mask
clip-path cuts a geometry: circle, inset, polygon. The edge is sharp. A mask can be soft. They are not two names for one property:
.avatar {
clip-path: circle(50%);
}
.poster {
mask-image: radial-gradient(circle, #000 55%, transparent 80%);
mask-repeat: no-repeat;
}
Avatars and tickets often want clip-path. Hero posters that dissolve into the page want a gradient mask. You can combine them, but one job per property is calmer. overflow: hidden with border-radius is a third tool: it clips descendants to the padding box. A mask paints the element itself, including its background, without rounding the layout box.
Safari and the WebKit prefix
Some Safari versions still want -webkit-mask-image (and friends) beside the standard properties:
.fade-photo {
-webkit-mask-image: linear-gradient(#000 70%, transparent);
mask-image: linear-gradient(#000 70%, transparent);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
}
Put the prefixed line and the standard line. Do not ship prefix-only forever. Feature queries (@supports) can wrap extras; the habit is: test the fade in Safari, not only in Chromium.
Mask vs clip vs overflow vs sprites
📊 Mask vs clip-path vs overflow vs sprites
| Tool | Job | Poor fit |
|---|---|---|
mask-image / mask-mode |
Soft holes, fades, shaped photos on one element | Hiding a whole form; replacing column layout |
clip-path |
Hard circle, polygon, inset silhouette | Soft dissolves; newspaper columns |
overflow: hidden |
Clip descendants to the box (often with radius) | Painting a gradient stencil on the element’s own pixels |
Sprites (background-position) |
Many icon tiles from one bitmap | Stenciling a hero photo; flowing article text |
Multi-column (column-*) |
Newspaper fill of one text stream | Visibility holes in a picture |
Optional Advanced already used sprites for icons and columns for prose. Masking is the stencil. Mixing all three on the same hero is how you get a photo that is both cropped like a sprite and sliced like a column.
What not to mask
Do not use a mask to “clean up” a messy layout. If a button or a label sits under the fade, people cannot read or tap it. Screen readers may still announce the text while sighted users see nothing — that is a bug, not a feature. Contrast still matters in the visible part. Decorative photos, cards, and icons are the usual targets; login fields are not.
.ok-art {
mask-image: linear-gradient(to right, #000, transparent);
}
.not-a-layout-fix {
/* don't: mask the entire toolbar because it overflowed */
}
.icons {
/* sprites still use background-position — not mask-image */
background-image: url("/img/icons.png");
background-position: 0 -32px;
}
If the problem is a child sticking out of a card, overflow (or a smaller child) is the layout fix. If the problem is twelve HTTP icon files, a sprite or SVG set is the asset fix. Masking does not replace either.
Try it yourself
- Give a colored box
mask-image: linear-gradient(to bottom, #000 50%, transparent)and watch the bottom dissolve. - Swap in a small SVG or PNG stencil; set
mask-sizeandmask-position: center. - Try
mask-mode: luminanceon a gray JPEG mask vsalphaon a transparent PNG. - Compare the same circle with
clip-path: circle(50%)— notice the hard edge. - Add
-webkit-mask-imagewith the same gradient. Do not mask a form.
Common mistakes
- Using a mask as a page layout or as
overflow: hidden - Forgetting
mask-sizeso a tiny stencil sits in the corner - Expecting a JPEG mask to punch holes without
mask-mode: luminance - Skipping
-webkit-mask-imageand blaming “CSS masks” in Safari - Treating
clip-pathandmask-imageas identical - Masking labels, captions, or buttons until contrast and hit targets die
- Confusing this with sprites (tile offsets) or multi-column (text streams)
Summary
- A mask is a stencil on one element: show here, hide there
mask-imagetakes a url or a gradient; size, repeat, and position mirror backgroundsmask-mode: alpha vs luminanceclip-pathis a hard geometric cut; masks can fade- Prefix for Safari; never mask away essential UI
- Optional Advanced continues next with user interface (
user-interface)
🧠 Test Your Knowledge
Test Your Knowledge
Challenge yourself with this interactive quiz and see how well you understand the topic
📝 Instructions
- Read each question carefully
- Select the best answer for each question
- You can retake the quiz as many times as you want
- Your progress will be shown at the top