CSS Clip & Masking
CSS clipping and masking control which parts of an element are visible. Clipping (clip-path) uses geometric shapes to define a visible region — everything outside the shape is hidden. Masking (mask-*) uses an image or gradient to control visibility based on pixel alpha values.
While both techniques hide portions of an element, they differ fundamentally: clip-path is a vector-based approach using paths and shapes, while masking is a raster-based approach using image data. Clipping is generally more performant for simple shapes.
| 1 | .clip-example { |
| 2 | clip-path: circle(50% at center); |
| 3 | /* Only shows a circular region of the element */ |
| 4 | } |
| 5 | |
| 6 | .mask-example { |
| 7 | mask-image: linear-gradient(black, transparent); |
| 8 | /* Fades out from top to bottom */ |
| 9 | } |
The clip-pathproperty creates a clipping region that defines which part of an element is visible. The element's background, borders, shadows, and even child content are all clipped to the defined shape.
| 1 | .element { |
| 2 | /* Basic clip path shapes */ |
| 3 | clip-path: none; /* no clipping (default) */ |
| 4 | clip-path: inset(10%); /* rectangle inset from edges */ |
| 5 | clip-path: circle(50%); /* circular clip */ |
| 6 | clip-path: ellipse(25% 40%); /* elliptical clip */ |
| 7 | clip-path: polygon(50% 0%, ...); /* custom polygon */ |
| 8 | clip-path: path('M0 0 ...'); /* SVG path (modern browsers) */ |
| 9 | |
| 10 | /* With position */ |
| 11 | clip-path: circle(30% at top right); |
| 12 | clip-path: polygon(0 0, 100% 0, 100% 75%, 50% 100%, 0 75%); |
| 13 | } |
The clip-path property accepts several shape functions, each with its own parameters for defining the clipping region.
inset()
Creates a rectangular clip region inset from the element's edges. Supports optional round for rounded corners.
| 1 | .inset { |
| 2 | /* inset(top, right, bottom, left) */ |
| 3 | clip-path: inset(10px 20px 10px 20px); |
| 4 | clip-path: inset(10%); /* 10% from all edges */ |
| 5 | clip-path: inset(0 round 12px); /* clipped to rounded rectangle */ |
| 6 | clip-path: inset(10% 5% 10% 5% round 8px); |
| 7 | } |
circle()
Creates a circular clip region defined by radius and center position.
| 1 | .circle { |
| 2 | clip-path: circle(50%); /* half the element size */ |
| 3 | clip-path: circle(30% at top left); /* centered at top-left */ |
| 4 | clip-path: circle(50px at 50% 50%); /* fixed radius at center */ |
| 5 | } |
ellipse()
| 1 | .ellipse { |
| 2 | clip-path: ellipse(25% 40%); /* wide ellipse */ |
| 3 | clip-path: ellipse(60px 30px at center); /* fixed dimensions */ |
| 4 | clip-path: ellipse(50% 50% at 0 50%); /* half-ellipse on left */ |
| 5 | } |
polygon()
Creates a custom polygonal clip region from a set of coordinate pairs. This is the most versatile shape function, capable of creating complex clipping regions.
| 1 | .triangle { |
| 2 | clip-path: polygon(50% 0%, 0% 100%, 100% 100%); |
| 3 | } |
| 4 | |
| 5 | .hexagon { |
| 6 | clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%); |
| 7 | } |
| 8 | |
| 9 | .star { |
| 10 | clip-path: polygon( |
| 11 | 50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, |
| 12 | 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35% |
| 13 | ); |
| 14 | } |
| 15 | |
| 16 | .chevron { |
| 17 | clip-path: polygon(0% 0%, 75% 0%, 100% 50%, 75% 100%, 0% 100%, 25% 50%); |
| 18 | } |
path()
The path() function accepts an SVG path data string, enabling arbitrary shapes defined by cubic and quadratic bezier curves.
| 1 | .custom-shape { |
| 2 | clip-path: path('M 0 0 L 100 0 L 100 100 L 50 80 L 0 100 Z'); |
| 3 | /* SVG path syntax for maximum flexibility */ |
| 4 | } |
clip-path can be animated when transitioning between compatible shapes (same shape type with matching number of points for polygons). This enables smooth morphing effects.
| 1 | .morph { |
| 2 | clip-path: circle(30%); |
| 3 | transition: clip-path 0.5s ease; |
| 4 | } |
| 5 | |
| 6 | .morph:hover { |
| 7 | clip-path: circle(70%); |
| 8 | /* Smoothly grows the circle on hover */ |
| 9 | } |
| 10 | |
| 11 | .polygon-morph { |
| 12 | clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%); |
| 13 | transition: clip-path 0.5s ease; |
| 14 | } |
| 15 | |
| 16 | .polygon-morph:hover { |
| 17 | clip-path: polygon(20% 0%, 80% 0%, 100% 80%, 0% 100%); |
| 18 | /* Morphs to an irregular quadrilateral */ |
| 19 | } |
| 20 | |
| 21 | @keyframes reveal { |
| 22 | 0% { clip-path: circle(0% at center); } |
| 23 | 100% { clip-path: circle(100% at center); } |
| 24 | } |
| 25 | |
| 26 | .reveal-anim { |
| 27 | animation: reveal 0.8s ease-out forwards; |
| 28 | } |
note
The mask property and its sub-properties use image data to determine which parts of an element are visible. Unlike clip-path which uses hard vector shapes, masking uses alpha channels for smooth transitions and gradient-based fades.
| 1 | .element { |
| 2 | /* Mask with a linear gradient — smooth fade */ |
| 3 | mask-image: linear-gradient(black, transparent); |
| 4 | -webkit-mask-image: linear-gradient(black, transparent); |
| 5 | |
| 6 | /* Multiple mask shorthand properties */ |
| 7 | mask-mode: alpha; /* use alpha channel (default) */ |
| 8 | mask-mode: luminance; /* use luminance values */ |
| 9 | |
| 10 | mask-repeat: no-repeat; |
| 11 | mask-position: center; |
| 12 | mask-size: cover; |
| 13 | |
| 14 | /* Shorthand */ |
| 15 | mask: url('mask.png') alpha center / cover no-repeat; |
| 16 | } |
mask-image defines the image used as the mask. The alpha channel (or luminance, depending on mask-mode) determines visibility: opaque regions of the mask show the element, transparent regions hide it.
| 1 | .gradient-fade { |
| 2 | /* Linear gradient mask */ |
| 3 | mask-image: linear-gradient(to bottom, black 60%, transparent 100%); |
| 4 | -webkit-mask-image: linear-gradient(to bottom, black 60%, transparent 100%); |
| 5 | /* Fades out the bottom 40% of the element */ |
| 6 | } |
| 7 | |
| 8 | .radial-reveal { |
| 9 | mask-image: radial-gradient(ellipse at center, black 30%, transparent 70%); |
| 10 | -webkit-mask-image: radial-gradient(ellipse at center, black 30%, transparent 70%); |
| 11 | /* Reveals the center of the element in a soft vignette */ |
| 12 | } |
| 13 | |
| 14 | .image-mask { |
| 15 | mask-image: url('mask-texture.png'); |
| 16 | -webkit-mask-image: url('mask-texture.png'); |
| 17 | mask-size: cover; |
| 18 | } |
| 19 | |
| 20 | /* Useful gradients for masking */ |
| 21 | .fade-right { |
| 22 | mask-image: linear-gradient(to right, black, transparent); |
| 23 | } |
| 24 | |
| 25 | .fade-all-sides { |
| 26 | mask-image: radial-gradient(ellipse 70% 60% at 50% 50%, black 30%, transparent 70%); |
| 27 | } |
The mask sub-properties mirror the background-* properties and control how the mask image is applied and rendered.
| 1 | .mask-demo { |
| 2 | /* mask-mode — determines which channel to use */ |
| 3 | mask-mode: alpha; /* use alpha channel (default) */ |
| 4 | mask-mode: luminance; /* use luminance values */ |
| 5 | mask-mode: match-source; /* auto-detect from image type */ |
| 6 | |
| 7 | /* mask-clip — clipping area for the mask */ |
| 8 | mask-clip: border-box; /* default */ |
| 9 | mask-clip: content-box; |
| 10 | mask-clip: padding-box; |
| 11 | |
| 12 | /* mask-composite — combine multiple mask layers */ |
| 13 | mask-composite: add; /* default — union */ |
| 14 | mask-composite: subtract; /* subtract from below */ |
| 15 | mask-composite: intersect; /* intersection */ |
| 16 | mask-composite: exclude; /* exclude overlapping areas */ |
| 17 | } |
info
Image Reveal
| 1 | .image-reveal { |
| 2 | position: relative; |
| 3 | overflow: hidden; |
| 4 | } |
| 5 | |
| 6 | .image-reveal img { |
| 7 | clip-path: circle(0% at center); |
| 8 | transition: clip-path 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); |
| 9 | } |
| 10 | |
| 11 | .image-reveal:hover img { |
| 12 | clip-path: circle(100% at center); |
| 13 | } |
| 14 | |
| 15 | /* Gradient reveal alternative */ |
| 16 | .gradient-reveal { |
| 17 | mask-image: linear-gradient(to right, transparent 0%, black 20%, black 80%, transparent 100%); |
| 18 | -webkit-mask-image: linear-gradient(to right, transparent 0%, black 20%, black 80%, transparent 100%); |
| 19 | mask-size: 200% 100%; |
| 20 | mask-position: 100% 0; |
| 21 | transition: mask-position 0.5s ease; |
| 22 | } |
| 23 | |
| 24 | .gradient-reveal:hover { |
| 25 | mask-position: 0 0; |
| 26 | } |
Terminal Window Shapes
| 1 | .terminal-window-clip { |
| 2 | clip-path: polygon( |
| 3 | 12px 0%, /* top-left corner radius */ |
| 4 | 100% 0%, |
| 5 | 100% 100%, |
| 6 | 0% 100%, |
| 7 | 0% 12px |
| 8 | ); |
| 9 | /* Creates a terminal-like window with clipped top-left corner area */ |
| 10 | /* More accurately, use border-radius for actual terminal windows */ |
| 11 | border-radius: 8px; |
| 12 | } |
| 13 | |
| 14 | /* Terminal scanline mask overlay */ |
| 15 | .scanline-mask { |
| 16 | position: absolute; |
| 17 | inset: 0; |
| 18 | background: repeating-linear-gradient( |
| 19 | 0deg, |
| 20 | transparent 0px, |
| 21 | transparent 2px, |
| 22 | rgba(0, 0, 0, 0.1) 2px, |
| 23 | rgba(0, 0, 0, 0.1) 4px |
| 24 | ); |
| 25 | mix-blend-mode: overlay; |
| 26 | pointer-events: none; |
| 27 | } |
| 28 | |
| 29 | /* Hexagonal grid layout for terminal dashboard */ |
| 30 | .hex-clip { |
| 31 | clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%); |
| 32 | transition: clip-path 0.3s ease; |
| 33 | } |
| 34 | |
| 35 | .hex-clip:hover { |
| 36 | clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 100% 100%, 0% 100%, 0% 50%); |
| 37 | } |
Smooth Scroll Reveal
| 1 | .scroll-reveal { |
| 2 | mask-image: linear-gradient(to right, transparent 0%, black 50%, transparent 100%); |
| 3 | -webkit-mask-image: linear-gradient(to right, transparent 0%, black 50%, transparent 100%); |
| 4 | mask-size: 200% 100%; |
| 5 | animation: sweep-reveal 1.5s ease-out forwards; |
| 6 | } |
| 7 | |
| 8 | @keyframes sweep-reveal { |
| 9 | 0% { mask-position: -100% 0; } |
| 10 | 100% { mask-position: 0% 0; } |
| 11 | } |
| 12 | |
| 13 | /* Diagonal reveal */ |
| 14 | .diagonal-reveal { |
| 15 | clip-path: polygon(0 100%, 100% 100%, 100% 100%, 0 100%); |
| 16 | transition: clip-path 0.6s ease-out; |
| 17 | } |
| 18 | |
| 19 | .diagonal-reveal.visible { |
| 20 | clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%); |
| 21 | } |