ToolPal
Abstract geometric shapes in purple and blue

CSS Clip Path Generator: Create Custom Shapes Without the Headache

πŸ“· Magicle Studio / Pexels

CSS Clip Path Generator: Create Custom Shapes Without the Headache

A practical guide to CSS clip-path β€” how to create polygons, circles, and custom shapes using a visual generator, with real examples and browser compatibility notes.

April 1, 202610 min read

The First Time I Noticed a Hexagon Avatar

The first time I really stopped and stared at a website trying to figure out how was when I saw hexagonal user avatars on a gaming leaderboard. Not circular β€” hexagonal. Six-sided. My immediate thought was that they must have used a custom PNG mask or some canvas trick. Turns out it was six lines of CSS.

That was my introduction to clip-path, and it genuinely changed how I think about shapes in web design. Before, "shape" meant border-radius, SVG, or images. Now the browser itself is your cutting tool.


A Quick History: How We Got Here

Before clip-path was a CSS property, creating non-rectangular shapes meant jumping through some painful hoops. You could use SVG clipPath elements and reference them from your CSS, which worked but required you to define shapes in a completely different syntax in a different part of your markup. Alternatively, you'd use overflow: hidden with clever positioning and border-radius hacks β€” which gave you rounded shapes but nothing truly angular or arbitrary.

The CSS clip-path property standardized this. Instead of juggling SVG, you write your shape definition directly in CSS. The browser figures out the masking. It's one of those features that makes you retroactively annoyed at how much time older approaches wasted.


The Shape Functions, One by One

polygon() β€” The Workhorse

polygon() is the one you'll use most. It takes a list of coordinate pairs defining the vertices of a shape. By default, coordinates are percentages of the element's width and height.

A basic triangle:

.triangle {
  clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
}

That reads as: top-center, bottom-left, bottom-right. Three points, one triangle.

A parallelogram (great for diagonal section breaks):

.parallelogram {
  clip-path: polygon(10% 0%, 100% 0%, 90% 100%, 0% 100%);
}

A hexagon β€” the shape that started my whole clip-path journey:

.hexagon {
  clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
}

Six points, six lines of coordinate pairs. That's the whole thing.

The syntax gets verbose fast when you have a lot of points, which is exactly why a visual generator (more on that later) is so useful. Manually computing vertices for a star shape, for instance, means doing some trigonometry you definitely don't want to do by hand.

A five-pointed star, for reference:

.star {
  clip-path: polygon(
    50% 0%, 61% 35%, 98% 35%, 68% 57%,
    79% 91%, 50% 70%, 21% 91%, 32% 57%,
    2% 35%, 39% 35%
  );
}

Ten coordinate pairs. Definitely not something you want to type manually.

circle() β€” Simpler, Less Flexible

.avatar {
  clip-path: circle(50%);
}

This clips the element to a circle with a radius of 50% β€” effectively the same as border-radius: 50% but without affecting layout. You can also specify the center point:

.off-center-circle {
  clip-path: circle(40% at 60% 50%);
}

Honest take: border-radius is still usually the better choice for simple circular images. clip-path: circle() makes more sense when you need the circle to interact with other clip-path effects, or when you want to animate it (more on animation gotchas below).

ellipse() β€” Circle's Elongated Cousin

.ellipse-crop {
  clip-path: ellipse(60% 40% at 50% 50%);
}

The first two values are horizontal and vertical radii. Useful for oval image crops or creating lens-flare-style shapes. Not something I reach for often, but when the design calls for an oval, it's there.

inset() β€” The Underrated One

This one doesn't get nearly enough attention. inset() clips the element to a rectangular region defined by inset values from each edge β€” like a padding in reverse:

.inset-crop {
  clip-path: inset(10px 20px 10px 20px);
  /* top right bottom left */
}

You can also add border-radius to the inset rectangle:

.rounded-inset {
  clip-path: inset(10% round 12px);
}

Where inset() really shines is for card cutouts and notch effects β€” those designs where a piece of a card appears to be "punched out" at a corner. Hard to do with standard CSS, trivial with inset() combined with a creative background.


Real-World Use Cases

Diagonal Hero Section Breaks

The tilted separator between a hero section and the content below it is everywhere. Here's how it's done:

.hero {
  clip-path: polygon(0 0, 100% 0, 100% 85%, 0 100%);
}

This clips the bottom edge of the hero at an angle, creating a diagonal cut. The content below flows into the space naturally. Much cleaner than the old "rotated pseudo-element" trick.

Hexagonal User Avatars

The classic. Apply to an img tag directly:

.avatar-hex {
  width: 120px;
  height: 120px;
  clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
  object-fit: cover;
}

The object-fit: cover ensures the image fills the space before clipping. Without it, you get letterboxing.

Angled Card Decorations

A card with a clipped bottom-right corner β€” a subtle touch that reads as polished:

.notched-card {
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 20px), calc(100% - 20px) 100%, 0 100%);
}

The Two Gotchas That Will Bite You

Gotcha 1: clip-path Eats Your Box Shadow

This one trips up nearly everyone the first time. You add a nice drop shadow to a card, apply clip-path for that diagonal cut, and the shadow disappears. That's because clip-path clips everything outside the shape boundary β€” including shadows, which render outside the element's box.

The fix is filter: drop-shadow():

/* This gets clipped away */
.broken {
  clip-path: polygon(0 0, 100% 0, 100% 85%, 0 100%);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
}

/* This works correctly */
.fixed {
  filter: drop-shadow(0 8px 24px rgba(0, 0, 0, 0.2));
  clip-path: polygon(0 0, 100% 0, 100% 85%, 0 100%);
}

filter: drop-shadow() applies after compositing, following the actual visible shape outline. The trade-off is that filter: drop-shadow() doesn't support inset shadows or multiple shadow layers β€” but for the shadow-on-clipped-element case, it's the right tool. You can pair it with the CSS Box Shadow generator to design your shadow and then translate the values.

Gotcha 2: Hover and Click Areas Follow the Clip

This is logical once you think about it, but it surprises people. The clipped region is visually gone and functionally gone. Mouse events don't fire in the clipped area. If your design clips away a corner where a user might reasonably try to click, that click won't register.

For most cases this doesn't matter β€” you're clipping visual decorations. But for interactive elements with complex shapes, test your hit areas carefully. There's no native CSS solution to have "visual clip, full hit area" β€” it requires JavaScript workarounds or restructuring your markup.


Animating clip-path (and When It Doesn't Work)

The good news: clip-path is animatable. The browser can interpolate between two polygon shapes smoothly:

.btn {
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
  transition: clip-path 0.3s ease;
}

.btn:hover {
  clip-path: polygon(0 0, 100% 0, 85% 100%, 0 100%);
}

This animates a rectangular button to have a diagonal-cut corner on hover. Clean, smooth, no JavaScript.

The bad news: you can only animate between the same shape function with the same number of points. Trying to transition from a circle() to a polygon() produces a hard snap β€” no interpolation. And transitioning between two polygons with different point counts also fails.

If you need shape-morphing animations with different topologies, SVG clip-path (defined with a <clipPath> element) gives more flexibility because GSAP and other animation libraries can handle arbitrary SVG path interpolation. For simpler effects, the CSS approach works beautifully.


Responsive Clip-Path

A common question: does clip-path work responsively? Yes, if you use percentage-based values. The percentages are relative to the element's own dimensions, so the shape scales as the element scales.

/* This scales correctly */
.responsive-diagonal {
  clip-path: polygon(0 0, 100% 0, 100% 90%, 0 100%);
}

Fixed pixel values in clip-path do not scale:

/* This will look wrong on small screens */
.broken-responsive {
  clip-path: polygon(0 0, 800px 0, 800px 90%, 0 100%);
}

Stick to percentages for any shape that lives inside a fluid container. You can mix calc() with percentages for specific adjustments:

.notched {
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 30px), calc(100% - 30px) 100%, 0 100%);
}

Using the Generator

Typing polygon coordinates by hand is an exercise in frustration. A visual clip-path generator changes the workflow entirely β€” you drag handles on a preview, see the shape in real time, and copy the CSS when it looks right.

Here's how to get the most out of one:

  1. Start with a preset β€” most generators include triangle, hexagon, parallelogram, star, and other common shapes. Starting from a preset and then dragging handles is faster than building from scratch.

  2. Use the preview on real content β€” generate on a placeholder image or colored div that resembles your actual content. Shapes look very different on a solid color vs. a photo.

  3. Check the output on both desktop and mobile widths β€” a shape with shallow angles can look elegant on desktop and cramped on a phone. Percentage-based points help, but some shapes genuinely need different breakpoint values.

  4. Copy only what you need β€” a generator gives you the full clip-path property. If you're using it inside a component that also needs filter: drop-shadow(), paste both together.


Pairing clip-path With Other CSS Tools

clip-path rarely lives in isolation in real projects. Here are the common combinations:

  • CSS Gradients: Clip a gradient background to a polygon for geometric decorative sections. The gradient renders first, then the shape clips it.

  • CSS Flexbox: Flexbox handles the layout around your clipped elements. Clip-path doesn't affect document flow β€” the element still occupies its original rectangle in the layout. Other elements flow around the bounding box, not the visible shape.

  • CSS Box Shadow: Design your shadow values here, then translate to filter: drop-shadow() syntax when applying to clipped elements (since box-shadow gets clipped).


Browser Support: The Honest Picture

The basic clip-path shape functions β€” polygon(), circle(), ellipse(), inset() β€” have solid support across modern browsers. Chrome, Firefox, Safari, and Edge all support them without prefixes.

Where you still run into issues:

  • Older iOS Safari (pre-13.4) needed the -webkit- prefix for basic shapes
  • Very old Android WebView versions may have issues with complex polygon paths
  • SVG-based clip-path: url(#myClip) has better legacy coverage if you need to support older environments

For the vast majority of projects targeting modern browsers, you can use clip-path freely. If you're building something that needs to work on an unusual range of devices, test on BrowserStack before committing to complex clip-path effects.


Wrapping Up

clip-path is one of those CSS properties that feels almost too powerful once you get comfortable with it. Diagonal sections, geometric avatars, notched cards β€” things that used to require image assets or SVG workarounds now take a few lines of CSS.

The main things to remember:

  1. polygon() is the most versatile β€” use percentages for responsive behavior
  2. clip-path clips box-shadow β€” use filter: drop-shadow() instead
  3. Click and hover areas follow the clip boundary
  4. Animate only between same-function, same-point-count shapes
  5. When in doubt, use a visual generator β€” life's too short to compute hexagon vertices by hand

Frequently Asked Questions

Share this article

XLinkedIn

Related Posts