CSS clip-path Generator: Polygon, Circle and Inset Shapes
Master CSS clip-path with polygon, circle, and inset shapes. Includes a generator walkthrough, copy-paste code snippets, and animation tips for 2026.
What clip-path Actually Does
clip-path lets you mask any HTML element into an arbitrary shape — the pixels outside the path simply don't render. No SVG file required, no JavaScript, no canvas. Just one CSS declaration and your <div> becomes a hexagon, a chevron, or a wobbly blob.
Browsers have supported the basic shapes since Chrome 55 and Firefox 54 (back in 2017), so you're not betting on an experimental feature. Safari trailed slightly but has had solid support since version 13.1. The path() value — which accepts a raw SVG path string — is the last holdout for cross-browser parity, but polygon, circle, ellipse, and inset are rock-solid everywhere.
The property works on any element: images, divs, buttons, even <video> tags. That last one is underused. Clip a video into a hexagon and you instantly have something that looks bespoke, even if the underlying technique is dead simple.
Honestly, the mental model tripped me up the first time. You're not drawing a shape you see — you're drawing the region you keep. Everything outside gets thrown away. Once that clicks, the rest is just coordinate math.
The Four Shape Functions You'll Actually Use
`polygon()` is the workhorse. You give it a list of x/y coordinate pairs and it connects them in order. Coordinates can be percentages, px values, or a mix. clip-path: polygon(50% 0%, 100% 100%, 0% 100%) is a triangle. Add more points and you get as complex as you want.
/* Triangle pointing up */
.triangle {
clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
}
/* Parallelogram */
.parallelogram {
clip-path: polygon(15% 0%, 100% 0%, 85% 100%, 0% 100%);
}
/* Arrow chevron */
.chevron {
clip-path: polygon(0% 0%, 75% 0%, 100% 50%, 75% 100%, 0% 100%, 25% 50%);
}`circle()` takes a radius and an optional center point: clip-path: circle(50% at 50% 50%) is a perfect circle. Shift the center and you get interesting crop effects on images — great for profile photos where you want the face off-center. Worth noting: the radius can be a keyword too — closest-side or farthest-corner — which makes responsive circles easier.
`inset()` clips to a rectangle with optional rounded corners. clip-path: inset(10px 20px 10px 20px round 8px) gives you a rounded rectangle with 10px top/bottom inset and 20px left/right inset. It sounds boring but it's incredibly useful for cropping images to an exact region without overflow tricks.
`ellipse()` is the sibling of circle — two radii instead of one. clip-path: ellipse(50% 30% at center) makes a wide oval. You'll reach for this less often, but it's there when you need that egg shape on a hero image. One more thing — all four of these compose fine with transition and animation, which opens up the real fun.
Using a clip-path Generator (and When to Do the Math Yourself)
A visual generator saves you from iterating blind. You drag handles, watch the shape update in real time, copy the output. For polygon shapes with more than four or five points, this is basically mandatory — mentally tracking nine coordinate pairs while tweaking a star shape is not how you want to spend an afternoon.
The standard tools (Clippy, Bennett Feely's classic generator) give you a library of presets — arrow, star, cross, message bubble — plus drag handles for custom shapes. You pick a shape, adjust the points to fit your layout, copy the one-liner. That's the workflow for 80% of cases. If you want something that integrates tightly with the rest of your design system tokens — including the gradient backgrounds that make clipped shapes look best — the gradient generator at Empire UI pairs naturally with this kind of work.
That said, there are cases where you want the math. Repeating shapes across a grid, generating clip paths programmatically in JavaScript, or animating between two complex polygons — these all benefit from understanding the coordinate system. A generator shows you the output; knowing the formula lets you generate variants.
// Generate a regular n-gon polygon string in JS
function regularPolygon(sides, cx = 50, cy = 50, r = 50) {
const points = [];
for (let i = 0; i < sides; i++) {
const angle = (i * 2 * Math.PI) / sides - Math.PI / 2;
const x = (cx + r * Math.cos(angle)).toFixed(2);
const y = (cy + r * Math.sin(angle)).toFixed(2);
points.push(`${x}% ${y}%`);
}
return `polygon(${points.join(', ')})`;
}
// Pentagon
console.log(regularPolygon(5));
// polygon(50.00% 0.00%, 97.55% 34.55%, 79.39% 90.45%, 20.61% 90.45%, 2.45% 34.55%)In practice, I keep both approaches in my toolkit. Generator for one-offs, math for anything parameterized. The 30 seconds you spend understanding the formula pays back every time you need a seven-sided shape on mobile that's slightly different from desktop.
Animating clip-path Without Making People Sick
Here's the thing most tutorials miss: you can only animate between two clip-path values of the same type with the same number of points. Trying to transition from a circle() to a polygon() — or a triangle to a hexagon — won't interpolate. The browser just snaps between them.
The workaround for cross-shape animation is to represent every shape as a polygon with the same point count, just with some points collapsed to the same coordinate. A circle becomes a polygon with 32 points all lying on a circle. A diamond becomes a polygon with 32 points where most are collapsed to four corners. Then you can transition between them smoothly.
.shape {
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
transition: clip-path 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.shape:hover {
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
}Quick aside: cubic-bezier(0.34, 1.56, 0.64, 1) is a spring-like ease with a slight overshoot. On shape morphs it adds a satisfying snap that straight ease-in-out never gives you. Steal it. If you're building UI components with strong visual character — clipped cards, angled section breaks, shaped avatars — check out the neobrutalism style hub, where angular clip paths show up constantly as a design primitive.
Performance is fine. clip-path doesn't trigger layout or paint on its own — only composite. That means you can animate it at 60fps without a second thought, same as transform and opacity. What you want to avoid is animating clip-path alongside properties that do trigger layout (like width or padding). Keep the animated element on its own stacking context and you're good.
Clipping Images vs. Clipping Colored Boxes
The visual impact is completely different and it's worth treating them separately. Clipping a solid-color box gives you a geometric UI element — a shaped badge, an angled card footer, a cut-corner button. Clipping an image gives you a shaped photo crop that can feel editorial or playful depending on context.
For images, the most useful patterns are: circle crops for avatars (obviously), polygon crops for magazine-style layouts where photos bleed into adjacent columns, and inset with round for cards where you want tighter control than border-radius alone gives you. A 40px inset with round 20px is subtly different from just border-radius: 20px — it actually removes those pixels rather than just rounding them, which matters when you have a box-shadow you don't want clipped.
/* Diagonal image crop — photo bleeds into angled section below */
.hero-image {
clip-path: polygon(0 0, 100% 0, 100% 85%, 0 100%);
}
/* Asymmetric avatar with personality */
.avatar {
clip-path: polygon(
50% 0%, 90% 20%, 100% 60%, 75% 100%,
25% 100%, 0% 60%, 10% 20%
);
}Look, the temptation is to go wild with complex shapes everywhere. Don't. One clipped element per section is a design choice. Five clipped elements per section is noise. The shapes work because they contrast with the rectilinear grid around them — saturate the page and that contrast disappears.
If you want shapes to feel cohesive rather than random, build a small set of two or three clip-path values that repeat across the design. An angled hero, matching angled section breaks, and a uniform avatar shape — that's a system. Random hexagons and stars mixed together is not. Browse the glassmorphism components to see how constrained shape vocabulary creates visual coherence even when the components themselves vary.
Common Gotchas and How to Fix Them
clip-path clips everything — including box-shadow and outline. This surprises basically everyone the first time. Your beautifully crafted box-shadow: 0 8px 32px rgba(0,0,0,0.3) just disappears because the shadow renders outside the element boundary and gets clipped. The fix: wrap the element in a parent div and apply the shadow to the parent, or switch to filter: drop-shadow() which follows the clipped contour.
/* box-shadow gets clipped — don't do this */
.clipped-wrong {
clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
box-shadow: 0 8px 32px rgba(0,0,0,0.3); /* invisible */
}
/* filter: drop-shadow follows the clip path */
.clipped-right {
clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
filter: drop-shadow(0 8px 16px rgba(0,0,0,0.3)); /* works */
}Overflow also gets weird. overflow: hidden on a clipped element is redundant — clip-path already handles it — but child elements that overflow the clipping region will be invisible without any obvious reason why. If a tooltip or dropdown inside a clipped container disappears at the edges, that's what's happening. Move the overflow content outside the clipped element.
The last one trips up Tailwind users specifically. As of Tailwind v3.3, there's no built-in clip-path utility. You're writing inline styles or adding to theme.extend in tailwind.config.js. Arbitrary values work — [clip-path:polygon(0_0,100%_0,100%_85%,0_100%)] — but they get unreadable fast for anything beyond a simple shape. For complex polygons, a CSS custom property or a named class is way easier to maintain.
/* Clean approach: CSS custom properties for repeated shapes */
:root {
--clip-chevron: polygon(0% 0%, 75% 0%, 100% 50%, 75% 100%, 0% 100%, 25% 50%);
--clip-hero: polygon(0 0, 100% 0, 100% 88%, 0 100%);
}
.hero { clip-path: var(--clip-hero); }
.tag { clip-path: var(--clip-chevron); }FAQ
No — the browser can only interpolate between values of the same type with the same point count. To morph between shapes, represent both as polygons with equal numbers of points, collapsing extra points as needed.
clip-path clips the entire element including its shadow. Switch to filter: drop-shadow() instead — it traces the clipped contour rather than the element box.
Yes. Polygon, circle, ellipse, and inset have been supported in all major browsers since 2017. The only value to watch is path(), which still has minor gaps — stick to the basic shapes and you're fine.
Barely. It runs on the compositor, so it doesn't trigger layout or paint. Animate it freely — just don't combine it with layout-triggering properties like width or height in the same transition.