What is CSS Clip Path?
The clip-path CSS property creates a clipping region that defines which part of an element should be visible. Think of it as a pair of scissors that cuts out a specific shape from an element, hiding everything outside that shape while revealing everything inside it. The element itself still occupies its original space in the layout and remains fully interactive within the clipped region β clicks, hovers, and focus events still fire as expected.
At its core, clip-path accepts shape functions or an SVG reference that defines the clipping mask. The property works on any HTML element, including images, divs, text blocks, and even video elements. It is fully animatable, hardware-accelerated in most browsers, and plays beautifully with other CSS properties like transforms, filters, and transitions.
The basic syntax looks like this:
/* Using a built-in shape function */
.element {
clip-path: circle(50%);
}
/* Using an SVG clipPath reference */
.element {
clip-path: url(#myClipPath);
}
/* Using a polygon with custom coordinates */
.element {
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}
/* Resetting to no clipping */
.element {
clip-path: none;
}
The property supports several shape functions out of the box: inset(), circle(), ellipse(), polygon(), and path(). Each one offers a different level of control, from simple rectangles with rounded corners to complex BΓ©zier curves imported straight from SVG syntax.
Why Clip Path Matters in Modern Web Design
π Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Clip path has quietly become one of the most powerful tools in a front-end developer's arsenal. Here's why it deserves your attention:
- Non-destructive masking β Unlike manually cropping images in Photoshop, clip path leaves the original asset intact. You can change the shape at any time via CSS without touching the source file.
- Performance β Clip path operations are GPU-accelerated in modern browsers, making animations smooth even on mobile devices. The browser compiles clip paths into efficient geometry that runs on the compositor thread.
- Responsive by nature β Percentage-based coordinates in
polygon()and relative values incircle()adapt automatically to element dimensions, eliminating the need for JavaScript-driven recalculations on resize. - Interactive hit areas β The clipped element remains fully clickable and focusable within the visible region, which is perfect for custom buttons, cards, and navigation items with unconventional shapes.
- Creative freedom β From diagonal section dividers to organic blob shapes on hero images, clip path enables visual treatments that would otherwise require complex SVG overlays or canvas manipulation.
- Zero DOM footprint β Unlike SVG-based clipping that requires extra markup, CSS clip path adds zero nodes to your document tree, keeping your HTML clean and semantic.
How to Use CSS Clip Path
Basic Shapes: inset(), circle(), and ellipse()
The simplest way to start is with the built-in shape functions. These cover most common use cases and require minimal code.
inset() clips to a rectangle inset from the edges of the element's bounding box. It accepts up to four offset values (mimicking the margin/padding shorthand) plus an optional border-radius for rounded corners:
/* Clip to a rectangle 20px inset from all edges with 10px rounded corners */
.card-clipped {
clip-path: inset(20px round 10px);
}
/* Asymmetric insets: top right bottom left */
.asymmetric-clip {
clip-path: inset(40px 10px 20px 30px round 15px);
}
/* Inset from just top and bottom, leaving full width */
.strip-clip {
clip-path: inset(25% 0);
}
circle() creates a circular clipping region. You specify the radius (or use a percentage) and optionally position the center using at:
/* Circle with radius 75px centered in the element */
.profile-avatar {
width: 150px;
height: 150px;
clip-path: circle(75px at 50% 50%);
}
/* Circle with radius 40% of the element's width, centered at top-left */
.offset-circle {
clip-path: circle(40% at 20px 30px);
}
/* A circle that covers the entire element (radius = half the shorter dimension) */
.full-circle {
clip-path: circle(50%);
}
ellipse() works like circle() but accepts two radius values β one for the horizontal axis and one for the vertical:
/* Horizontal radius 100px, vertical radius 60px, centered */
.oval-clip {
clip-path: ellipse(100px 60px at 50% 50%);
}
/* Percentage-based ellipse that adapts to element size */
.responsive-ellipse {
clip-path: ellipse(45% 35% at center);
}
/* Ellipse positioned at the bottom-right corner */
.corner-ellipse {
clip-path: ellipse(30% 40% at 100% 100%);
}
Polygon for Complex Shapes
The polygon() function is where clip path truly shines. You define a series of x,y coordinate pairs that form the vertices of your clipping shape. Coordinates can be absolute pixel values or percentages relative to the element's bounding box. The browser connects the points in order and fills the resulting shape.
Here's a classic hexagonal clip:
.hexagon {
width: 200px;
height: 200px;
background: linear-gradient(135deg, #667eea, #764ba2);
clip-path: polygon(
50% 0%,
100% 25%,
100% 75%,
50% 100%,
0% 75%,
0% 25%
);
}
Diagonal section dividers are another extremely common pattern. This creates a slanted edge at the bottom of a hero section:
.hero-section {
height: 80vh;
background: url('hero-image.jpg') center/cover;
clip-path: polygon(0 0, 100% 0, 100% 85%, 0 100%);
}
You can go even further with starbursts, arrows, and abstract shapes:
/* An eight-point starburst */
.starburst {
clip-path: polygon(
50% 0%, 61% 35%, 98% 35%, 68% 57%,
79% 91%, 50% 70%, 21% 91%, 32% 57%,
2% 35%, 39% 35%
);
}
/* A right-pointing arrow / chevron */
.arrow-clip {
clip-path: polygon(
0% 0%, 85% 0%, 100% 50%, 85% 100%, 0% 100%, 10% 50%
);
}
/* A chat bubble tail */
.bubble {
clip-path: polygon(
0% 0%, 100% 0%, 100% 80%, 25% 80%, 15% 100%, 10% 80%, 0% 80%
);
}
A crucial detail: the polygon() function accepts an optional fill rule β either nonzero (default) or evenodd β specified before the coordinate list:
/* Using evenodd fill rule for self-intersecting shapes */
.complex-shape {
clip-path: polygon(
evenodd,
0% 0%, 100% 0%, 100% 100%, 0% 100%,
25% 25%, 75% 25%, 75% 75%, 25% 75%
);
}
SVG Path for Ultimate Control
When the built-in shape functions aren't enough, the path() function lets you use full SVG path syntax directly in CSS. This unlocks BΓ©zier curves, arcs, and complex organic shapes that would be impossible to express with polygons alone.
/* A smooth blob shape using cubic BΓ©zier curves */
.blob-clip {
clip-path: path('M 50 0 C 80 10 95 35 95 60 C 95 85 75 100 50 100 C 20 100 0 85 0 60 C 0 35 15 10 50 0 Z');
}
/* A wave shape at the bottom of a banner */
.wave-banner {
clip-path: path('M 0 0 L 1920 0 L 1920 700 Q 1440 800 960 750 Q 480 700 0 800 Z');
}
/* Curved notch on a card */
.notched-card {
clip-path: path('M 0 0 L 100% 0 L 100% 80% C 80% 90% 60% 100% 50% 100% C 40% 100% 20% 90% 0 80% Z');
}
You can also reference an external or inline SVG <clipPath> element using the url() syntax. This approach is particularly useful for complex, reusable clip shapes that you want to keep centralized in your SVG sprite sheet:
<!-- In your HTML or SVG sprite -->
<svg width="0" height="0" style="position:absolute">
<defs>
<clipPath id="organicBlob" clipPathUnits="objectBoundingBox">
<path d="M0.5,0 C0.75,0.05 0.95,0.3 0.95,0.55 C0.95,0.8 0.75,0.95 0.5,0.95 C0.2,0.95 0.05,0.8 0.05,0.55 C0.05,0.3 0.2,0.05 0.5,0 Z"/>
</clipPath>
</defs>
</svg>
<!-- In your CSS -->
<style>
.blob-image {
clip-path: url(#organicBlob);
}
</style>
The clipPathUnits="objectBoundingBox" attribute is essential here β it makes the path coordinates scale relative to the element's bounding box (0β1 range), similar to percentage-based values. Without it, coordinates use the SVG's own coordinate system, which can lead to unexpected clipping at wrong sizes.
Animating Clip Path
Clip path transitions and animations are hardware-accelerated and incredibly smooth when you follow one critical rule: the number and type of vertices must remain consistent between animation keyframes. The browser can interpolate between matching point structures efficiently, but mismatched vertex counts will cause the animation to snap rather than interpolate smoothly.
Here's a hover effect that morphs a square card into a hexagon-like shape:
.morph-card {
width: 300px;
height: 300px;
background: url('portrait.jpg') center/cover;
clip-path: polygon(
0% 0%, 100% 0%, 100% 100%, 0% 100%
);
transition: clip-path 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.morph-card:hover {
clip-path: polygon(
10% 0%, 90% 0%, 100% 50%, 90% 100%, 10% 100%, 0% 50%
);
}
Notice that the hover state has 6 vertices while the default has 4. To make this transition smooth, both states must have the same vertex count. Here's the corrected version with 6 vertices in both states:
.morph-card {
width: 300px;
height: 300px;
background: url('portrait.jpg') center/cover;
clip-path: polygon(
0% 0%, 100% 0%, 100% 0%, 100% 100%, 0% 100%, 0% 0%
);
transition: clip-path 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.morph-card:hover {
clip-path: polygon(
10% 0%, 90% 0%, 100% 50%, 90% 100%, 10% 100%, 0% 50%
);
}
For keyframe animations, the same rule applies. This pulsing blob animation works because all keyframes share the same 8-point polygon structure:
@keyframes blobPulse {
0% {
clip-path: polygon(
50% 5%, 80% 15%, 95% 40%, 85% 75%,
55% 90%, 25% 85%, 10% 60%, 20% 25%
);
}
50% {
clip-path: polygon(
50% 15%, 75% 10%, 90% 35%, 80% 70%,
60% 95%, 30% 90%, 15% 65%, 25% 30%
);
}
100% {
clip-path: polygon(
50% 5%, 80% 15%, 95% 40%, 85% 75%,
55% 90%, 25% 85%, 10% 60%, 20% 25%
);
}
}
.pulsing-blob {
width: 400px;
height: 400px;
background: linear-gradient(135deg, #f093fb, #f5576c);
animation: blobPulse 3s ease-in-out infinite;
}
You can also animate between circle() and inset() or even polygon() β the browser handles the interpolation internally by converting shapes to a common path representation:
.shape-morph {
clip-path: circle(30% at 50% 50%);
transition: clip-path 0.6s ease;
}
.shape-morph:hover {
clip-path: polygon(
0% 20%, 20% 0%, 80% 0%, 100% 20%,
100% 80%, 80% 100%, 20% 100%, 0% 80%
);
}
Clip Path with Images and Complex Content
Clip path works beautifully on <img> elements, background images (via the element's background property), and even on <video>. Here's a gallery of practical examples:
/* Image with a triangular clip */
img.triangle-clip {
width: 300px;
height: 300px;
object-fit: cover;
clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
}
/* Video with a circular clip that reveals on hover */
video.circle-reveal {
clip-path: circle(25% at 50% 50%);
transition: clip-path 0.5s ease-out;
}
video.circle-reveal:hover {
clip-path: circle(50% at 50% 50%);
}
/* Card with a diagonal cut corner */
.card-diagonal-cut {
padding: 2rem;
background: #ffffff;
border-radius: 8px;
clip-path: polygon(0 0, 85% 0, 100% 15%, 100% 100%, 0 100%);
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}
/* Two overlapping clipped elements creating a composite shape */
.layer-top {
clip-path: polygon(0 0, 60% 0, 40% 100%, 0 100%);
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
One subtle gotcha: when clipping an <img> directly, the clipping region applies to the image element's box, not the intrinsic image dimensions. Always set explicit width and height or use object-fit: cover to ensure predictable clipping boundaries.
Best Practices and Tips
1. Prefer Percentage-Based Coordinates
Using percentages in polygon() and relative values in circle() / ellipse() makes your clip paths responsive out of the box. The shape automatically scales with the element, eliminating the need for recalculation scripts or complex media queries:
/* Good: responsive hexagon */
.responsive-hex {
clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
}
/* Avoid: fixed pixel values that break on resize */
.fixed-hex {
clip-path: polygon(100px 0px, 200px 50px, 200px 150px, 100px 200px, 0px 150px, 0px 50px);
}
2. Match Vertex Counts for Smooth Animation
As demonstrated earlier, always keep the same number of vertices across animation states. If you're transitioning from a rectangle (4 vertices) to a complex shape, pad the rectangle with duplicate or intermediate points to match the target vertex count. This ensures smooth interpolation rather than an abrupt snap at the transition boundary.
3. Use objectBoundingBox for SVG clipPath References
When referencing SVG <clipPath> elements, set clipPathUnits="objectBoundingBox" and use coordinates in the 0β1 range. This makes the clip scale proportionally with the HTML element, just like percentage-based polygon coordinates:
<clipPath id="responsiveClip" clipPathUnits="objectBoundingBox">
<path d="M0,0 L1,0 L1,0.8 L0.5,1 L0,0.8 Z"/>
</clipPath>
4. Combine Clip Path with Shadows and Filters
A common misconception is that box-shadow gets clipped along with the element. In reality, box-shadow is applied to the element's original unclipped box and then clipped. For a shadow that follows the clip shape, use filter: drop-shadow() instead:
/* Box shadow gets clipped β not what you want */
.clipped-wrong {
clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
/* Drop shadow follows the clip path β correct approach */
.clipped-right {
clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
filter: drop-shadow(0 10px 20px rgba(0,0,0,0.3));
}
5. Preserve Hit Areas with an Invisible Overlay
Elements retain interactivity only within the clipped region. If you need the entire original bounding box to remain clickable (for example, a clipped image inside a link), wrap the clipped element in a container and apply the link or event handlers to the container instead:
<a href="/profile" class="clickable-wrapper">
<img src="avatar.jpg" class="hex-avatar" alt="User profile" />
</a>
<style>
.clickable-wrapper {
display: block;
width: 200px;
height: 200px;
cursor: pointer;
}
.hex-avatar {
width: 100%;
height: 100%;
object-fit: cover;
clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
}
</style>
6. Use DevTools to Visualize and Debug
Modern browser DevTools allow you to inspect and even live-edit clip-path values. In Chrome and Firefox, selecting an element with a clip path shows the clipping region as a semi-transparent overlay on the element. You can click on the clip-path property value and use the shape editor to drag vertices interactively, which is invaluable for fine-tuning complex polygons.
7. Watch Out for anti-aliasing Quirks
At certain angles, clip path edges can exhibit sub-pixel anti-aliasing artifacts that leave a 1px visible gap between adjacent clipped elements. To mitigate this, apply a tiny negative margin or a 1px overlap between elements, or use will-change: clip-path to hint the browser to keep the element on the GPU compositing layer:
.adjacent-clipped-panels {
clip-path: polygon(0 0, 100% 5%, 100% 95%, 0 100%);
will-change: clip-path;
/* Slight overlap with the next element */
margin-bottom: -1px;
}
8. Avoid Clip Path on Excessively Large Elements
While clip path is GPU-accelerated, applying extremely complex path() functions with hundreds of vertices to very large elements (like full-viewport hero images) can still strain rendering performance, especially on low-end mobile devices. Profile with DevTools performance panels and consider pre-rendering complex static shapes as actual cropped images or using simpler polygon approximations for production.
9. Fallbacks for Older Browsers
Clip path enjoys excellent browser support (95%+ globally), but if you need to support very old browsers, provide a sensible fallback:
/* Fallback: show full element in older browsers */
.legacy-safe {
/* Older browsers ignore clip-path and see the full element */
clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
/* Alternative: use @supports for progressive enhancement */
}
@supports not (clip-path: polygon(0 0)) {
.legacy-safe {
/* Fallback styling, perhaps a simpler border-radius approach */
border-radius: 20px;
overflow: hidden;
}
}
10. Use CSS Custom Properties for Dynamic Clip Paths
You can leverage CSS variables to create dynamic, themeable clip paths that adapt based on user interaction or parent context:
.dynamic-clip {
--notch-depth: 30px;
clip-path: polygon(
0 0,
calc(100% - var(--notch-depth)) 0,
100% var(--notch-depth),
100% 100%,
0 100%
);
transition: --notch-depth 0.3s ease;
}
.dynamic-clip:hover {
--notch-depth: 60px;
}
Note that for transition to work with custom properties, you need to register the property using @property (in Chromium-based browsers) or use a JavaScript-driven approach for broader compatibility.
Conclusion
CSS clip path is far more than a simple masking tool β it's a gateway to creative, performant, and responsive visual design that lives entirely in your stylesheets. From hexagonal image galleries and animated blob heroes to diagonal section transitions and interactive hover morphs, mastering clip path gives you the power to break free from rectangular constraints without adding a single byte of extra markup.
The key takeaways are clear: use percentage-based coordinates for responsiveness, match vertex counts for smooth animations, leverage drop-shadow() for shape-following shadows, and always test across viewport sizes to catch edge cases early. With the built-in shape functions, SVG path integration, and GPU-accelerated animation support, clip path sits at the sweet spot of creative expression and production-ready reliability.
Start small β clip a profile avatar into a circle, then experiment with a polygon hover transition, and before long you'll find yourself reaching for clip path instinctively whenever a design calls for something beyond the rectangle. The browser support is here, the performance is solid, and the creative possibilities are virtually limitless.