The CSS clipping and masking article draws the distinction that decides which tool you reach for: clipping is binary, every pixel is fully shown or fully hidden with a hard edge, while masking is graded, letting transparency vary for soft fades and feathered edges. This workbook runs both. The first half is clip-path, from a circle to animated custom shapes; the second half is mask-image with gradients for fades and spotlights; and the finale combines them and covers the -webkit-prefixes and fallbacks real projects need. Clip for shape, mask for softness, and the last example shows why you often want both.
Shared setup
Every example uses this index.html. Only the styles.css changes between examples, so keep this file and swap the stylesheet:
<html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Clipping and masking</title> <link rel="stylesheet" href="styles.css"></head><body> <div class="stage"> <div class="box">Clip & mask</div> </div></body></html>
A shared base for the stage and box, so each styles.css below can focus on the one technique it teaches:
/* base.css rules — paste at the top of each example's styles.css */body { margin: 0; font-family: "Segoe UI", sans-serif; background: #f7f9fc; }.stage { display: flex; min-height: 100vh; align-items: center; justify-content: center; }.box { width: 300px; height: 300px; display: flex; align-items: center; justify-content: center; color: #fff; font-size: 1.4rem; font-weight: 700; /* a gradient stands in for a photo, so nothing needs downloading */ background: linear-gradient(135deg, #1e2331, #6f7580);}
1. clip-path with circle()
The simplest clip crops an element to a circle. circle(50%) takes a radius of half the box and centres it, turning a square into a disc with a hard edge.
/* styles.css — base rules above, then: */.box { clip-path: circle(50%);}
The element is still a 300 by 300 square as far as layout is concerned; clip-path only changes which of its pixels are painted. circle(50%) sets the radius to 50 percent of the box’s size, so the corners are clipped away and a circle remains, and you can shift the centre with circle(50% at 30% 30%) to crop off-centre. The edge is crisp because clipping is binary: a pixel is either inside the circle and fully shown or outside it and fully gone, with nothing in between. That hard edge is the whole character of clipping, and the reason you reach for masking when you want softness instead.
2. Ellipse and inset with rounded corners
ellipse() clips to an oval with independent horizontal and vertical radii, and inset() clips to a rectangle pulled in from the edges, optionally with rounded corners. Both are basic shapes like circle().
.box { /* an oval: wide radius, narrow radius */ clip-path: ellipse(45% 30% at 50% 50%);}/* swap the box rule for this to try inset instead */.box.alt { /* trim 20px off top/bottom, 40px off left/right, round the corners */ clip-path: inset(20px 40px round 24px);}
ellipse(45% 30%) gives the horizontal radius first and the vertical second, so a wider-than-tall oval, with at 50% 50% centring it. inset() works the other way, describing how far to cut in from each edge rather than a shape to keep: the four values read top, right, bottom, left like margins, and round 24px rounds the resulting rectangle’s corners. inset with round is the clean way to get rounded corners on something that also needs clipping, since it does in one property what border-radius plus overflow: hidden does in two.
3. Polygon: custom shapes from points
polygon() is where clipping becomes powerful: you list corner points as coordinate pairs and CSS connects them, so any outline you can describe with vertices becomes a shape. A diamond is four points.
.box { /* top, right, bottom, left points -> a diamond */ clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);}
Each pair is an x y position as a percentage of the box, starting from the top-left origin, so 50% 0% is top-centre, 100% 50% is right-middle, and so on around the shape. CSS draws straight lines between the points in order and keeps everything inside, so four points make a diamond, three make a triangle, and six make a hexagon. This is the tool behind angled section dividers, badges, and speech bubbles: any sequence of points becomes a custom outline, and unlike an image mask it stays razor-sharp at every zoom level because it is geometry, not pixels.
4. Polygon for arrows and banners
A practical use of polygon() is directional shapes: an arrow, a chevron, a ribbon end. These are the shapes that would otherwise need background images or extra markup.
.box { width: 340px; /* a right-pointing arrow banner */ clip-path: polygon( 0% 20%, /* top-left, indented down */ 75% 20%, /* top edge to where the point begins */ 75% 0%, /* up to the tip's top */ 100% 50%, /* the point, at right-middle */ 75% 100%, /* down to the tip's bottom */ 75% 80%, /* back to the bottom edge */ 0% 80% /* bottom-left */ );}
The seven points trace an arrow: a rectangular body from 20 to 80 percent vertically, with a triangular point jutting out to 100% 50% on the right. Reading a polygon like this is easier if you sketch the points on paper first, then translate each to a percentage pair. The payoff is that the arrow is one element with one property, fully responsive and styleable with any background, where the old approach needed a background PNG that could not recolour or scale cleanly. Section dividers, “next” buttons, and progress ribbons are all this technique.
5. Animating clip-path on hover
Because clip-path shapes with the same number of points can interpolate between each other, they animate smoothly. A hover that grows the clip is a clean reveal effect.
.box { clip-path: circle(35% at 50% 50%); transition: clip-path 0.5s ease; cursor: pointer;}.box:hover { /* grow the circle to reveal the whole element */ clip-path: circle(75% at 50% 50%);}
The transition on clip-path is what animates the reveal: on hover the circle’s radius jumps from 35 to 75 percent, and the browser tweens between them, so the content appears to iris open. For polygon animations the rule is stricter, since the start and end shapes must have the same number of points for CSS to match them up and interpolate, so a four-point diamond can morph into another four-point quad but not into a triangle. Animated clips power reveal-on-hover cards, expanding call-to-action buttons, and page-transition wipes, all with no JavaScript.
6. Masking with a gradient: the soft fade
Now the other half. mask-image uses an image’s alpha to control transparency per pixel, and a gradient makes a perfect built-in mask. A linear-gradient from opaque to transparent fades an element’s edge softly, which clipping cannot do.
.box { /* the mask: visible where white, transparent where transparent */ -webkit-mask-image: linear-gradient(to right, black 40%, transparent 100%); mask-image: linear-gradient(to right, black 40%, transparent 100%);}
The mask is read by opacity: where the gradient is opaque black the element shows fully, where the gradient is transparent the element is hidden, and in between it is partially transparent, which is the feathered fade. Here the box is solid for its first 40 percent then dissolves toward the right edge, an effect impossible with clip-path because clipping has no “partially visible”. The -webkit- prefix sits alongside the standard property because masking still needs it in several browsers, a compatibility detail Example 10 returns to. Gradient masks are the everyday way to fade images into a background with no separate PNG.
7. Radial-gradient masks: the spotlight
Swap the linear gradient for a radial one and the fade becomes a spotlight or vignette, revealing the centre and dissolving the edges into the page.
.box { -webkit-mask-image: radial-gradient(circle at center, black 40%, transparent 70%); mask-image: radial-gradient(circle at center, black 40%, transparent 70%);}
A radial gradient runs from its centre outward, so the mask is opaque in the middle 40 percent and fades to fully transparent by 70 percent, producing a soft circular vignette where the box melts into the background at its rim. Note the difference from Example 1’s circle() clip: that gave a hard-edged disc, while this gives a circle with a feathered halo, and the two examples side by side are the clearest illustration of clipping versus masking. Radial masks are how you make spotlight effects, focus highlights, and images that appear to glow into their surroundings.
8. Mask like a background: size, repeat, position
Mask properties mirror the background ones exactly, so mask-size, mask-repeat, and mask-position place a mask the way you would place a background image. This lets a small shape tile or sit precisely.
.box { /* a repeating dot pattern as the mask */ -webkit-mask-image: radial-gradient(circle, black 30%, transparent 32%); mask-image: radial-gradient(circle, black 30%, transparent 32%); -webkit-mask-size: 40px 40px; mask-size: 40px 40px; -webkit-mask-repeat: repeat; mask-repeat: repeat;}
Because the mask system borrows the background model wholesale, everything you know about background-size and background-repeat transfers: mask-size: 40px 40px makes each copy of the radial gradient a 40px tile, and mask-repeat: repeat tiles it across the box, punching the element into a grid of dots. The same properties let you position a single PNG mask, for instance a logo shape whose alpha channel cuts the element to that silhouette via mask-image: url(logo.png). Once you see masks as backgrounds that control transparency instead of colour, the whole property set is already familiar.
9. Masking text with background-clip
A special and popular case clips a background to the shape of text, so a gradient or image fills the letters themselves. It uses background-clip: text rather than mask, but it belongs to the same family.
/* this example needs its own markup: a heading */
<!-- index.html body --><div class="stage"> <h1 class="gradient-text">DATALAD</h1></div>
/* styles.css */body { margin: 0; font-family: "Segoe UI", sans-serif; background: #1e2331; }.stage { display: flex; min-height: 100vh; align-items: center; justify-content: center; }.gradient-text { font-size: 6rem; font-weight: 800; /* the background that will show through the letters */ background: linear-gradient(120deg, #6f7580, #eceff4, #6f7580); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; /* let the background show through */ color: transparent; /* fallback for the same effect */}
The trick has two halves. background-clip: text confines the element’s background to the shape of its text glyphs instead of its box, and making the text fill colour transparent lets that clipped background show through the letters. The result is gradient-filled or image-filled text, which is how headings get metallic sheens, gradient fills, or a photo peeking through the type. It still needs the -webkit- prefix widely, and -webkit-text-fill-color is the reliable way to make the letters transparent, with plain color: transparent as the fallback.
10. Combining clip and mask, with fallbacks
The finale layers both: a hard shape from clip-path and a soft fade from a gradient mask on the same element, plus the progressive-enhancement pattern that keeps older browsers usable.
.box { /* fallback FIRST: a plain rounded box for browsers without clip/mask */ border-radius: 16px; /* hard shape: clip to a hexagon */ clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%); /* soft fade: mask the bottom into transparency */ -webkit-mask-image: linear-gradient(to bottom, black 55%, transparent 100%); mask-image: linear-gradient(to bottom, black 55%, transparent 100%);}/* only apply the fancy shape where the browser supports it */@supports not (clip-path: polygon(0 0)) { .box { clip-path: none; } /* fall back to the rounded box */}
The two techniques stack because they act independently: clip-path first cuts the element to a hexagon with hard edges, then the gradient mask fades that hexagon’s lower portion into transparency, giving a shape that is both custom-cut and softly dissolved, something neither property could do alone. The border-radius declared first is the fallback that shows if nothing else applies, and the @supports query is the belt-and-braces version, explicitly turning off the clip where polygon() is unsupported so no browser is left with a broken shape. This is the article’s closing advice made concrete: enhance progressively, and give older browsers a plain but usable version.
Work through these and you have the whole article in practice: clip-path with circle, ellipse, inset, and polygon; custom shapes and arrows from points; animated clip reveals; gradient masks for linear fades and radial spotlights; the background-mirroring mask properties; text clipping with background-clip; and the combined clip-plus-mask finish with -webkit- prefixes and @supports fallbacks. The decision to carry away is the article’s own: reach for clipping when you want a hard-edged shape, reach for masking when you want transparency that varies, and layer them when a design wants both a shape and a soft edge at once.
See you soon.
[…] CSS Clipping and Masking: 10 Code-Along Examples […]
[…] CSS Clipping and Masking: 10 Code-Along Examples […]