css · clip-path
clip-path: shape() versus path()
Two CSS functions draw the same blob outline. One takes pixels, the other takes percentages. The syntax, the browser support, and the @supports pattern.
Published September 18, 2026
CSS can hide everything outside a shape you describe. For a circle that is one function call. For a blob it is a list of curves, and there are now two ways to write that list: path(), which takes an SVG path string, and shape(), which describes the same geometry in CSS terms.
They are not interchangeable. The difference that matters is the unit, and it decides whether your clip survives a resize.
Here is the outline used throughout this piece, straight out of the generator:
M413,84.5Q476,169,447,249.5Q418,330,349,383Q280,436,204,412Q128,388,96,314Q64,240,105,169.5Q146,99,220,74.5Q294,50,353.5,67.25Q413,84.5,413,84.5Z
One M, eight Q commands, one Z, on a 500 by 500 grid.
path(), and why it stops at one size
path() takes that string unchanged.
.blob {
clip-path: path("M413,84.5Q476,169,447,249.5Q418,330,349,383Q280,436,204,412Q128,388,96,314Q64,240,105,169.5Q146,99,220,74.5Q294,50,353.5,67.25Q413,84.5,413,84.5Z");
}
That works, and it works almost everywhere. MDN records path() as Baseline widely available since July 2020, and browser compatibility data puts clip-path with path() in Chrome and Edge 88, Firefox 71, Safari 13.1, Safari on iOS 13, Opera 74 and Samsung Internet 15.
The catch is in the specification rather than in support. MDN states it plainly: inside path() every length is implicitly in pixel units, and other units cannot be used. The numbers above are pixels. So this rule clips a 500 by 500 element exactly, and clips nothing else correctly. Make the element 900 pixels wide and the outline stays at 500, leaving four hundred pixels of content hanging outside it. Make it 320 and the clip cuts into the content instead.
The usual workarounds are a wrapper locked to 500 pixels, or a transform: scale() on the element that drags the clip along with it. Both work. Both are more machinery than a decorative shape deserves, and neither survives a fluid grid where the element has no fixed size at all.
shape(), which takes percentages
shape() describes the outline as a start point plus a list of commands. Where SVG writes Q with the control point first, shape() writes curve to <end> with <control>, and the closing Z becomes close.
Divide every coordinate by 500 and the numbers become percentages: 413 is 82.6%, 84.5 is 16.9%, and so on around the shape.
.blob {
clip-path: shape(
from 82.6% 16.9%,
curve to 89.4% 49.9% with 95.2% 33.8%,
curve to 69.8% 76.6% with 83.6% 66%,
curve to 40.8% 82.4% with 56% 87.2%,
curve to 19.2% 62.8% with 25.6% 77.6%,
curve to 21% 33.9% with 12.8% 48%,
curve to 44% 14.9% with 29.2% 19.8%,
curve to 70.7% 13.45% with 58.8% 10%,
close
);
}
Percentages in shape() resolve against the width and the height of the reference box, so the outline is now proportional. The element can be 320 pixels wide on a phone and 900 in a wide column, and the blob arrives at the same relative position in both.
shape() has more commands than this blob needs: line to, hline, vline, arc to with a radius, smooth, move, and a two-control-point form of curve for cubic segments. A blob only ever needs curve to ... with and close, which makes the translation from SVG mechanical.
One consequence to plan for: percentages stretch on each axis independently. On a very wide, short element the blob is squashed, not cropped. If the proportions have to hold, give the element an aspect-ratio.
Browser support, with numbers
shape() is new. MDN lists it as Baseline newly available since February 2026, and the compatibility data gives Chrome 135, Edge 135, Safari 18.4, Safari on iOS 18.4, Opera 120, Samsung Internet 29 and Firefox 148. Caniuse currently puts that at about 87 percent of tracked global traffic.
Eighty-seven percent is enough to use and not enough to use alone. clip-path itself sits at roughly 97 percent on caniuse, so the gap is real: there are visitors whose browser understands clip-path but not shape(), and for them an unguarded shape() declaration is simply dropped, leaving the element unclipped.
The @supports pattern
Ship both. Put path() first so it applies everywhere, then override it inside an @supports block that tests for the function itself:
.blob {
width: 100%;
max-width: 500px;
aspect-ratio: 1;
clip-path: path("M413,84.5Q476,169,447,249.5Q418,330,349,383Q280,436,204,412Q128,388,96,314Q64,240,105,169.5Q146,99,220,74.5Q294,50,353.5,67.25Q413,84.5,413,84.5Z");
}
@supports (clip-path: shape(from 0% 0%, close)) {
.blob {
clip-path: shape(
from 82.6% 16.9%,
curve to 89.4% 49.9% with 95.2% 33.8%,
curve to 69.8% 76.6% with 83.6% 66%,
curve to 40.8% 82.4% with 56% 87.2%,
curve to 19.2% 62.8% with 25.6% 77.6%,
curve to 21% 33.9% with 12.8% 48%,
curve to 44% 14.9% with 29.2% 19.8%,
curve to 70.7% 13.45% with 58.8% 10%,
close
);
}
}
Test for the declaration, never for a browser. @supports asks the parser a direct question it can only answer one way, and needs no maintenance as support widens. Note the max-width and aspect-ratio on the base rule: they keep the fallback honest by holding the element near the 500-pixel grid the path was drawn on, while the shape() version can ignore them.
Clipping an image
The most common use is a photograph. Put the class on the img element itself.
<img class="blob" src="/team.jpg" alt="The workshop floor" />
.blob {
width: 100%;
aspect-ratio: 1;
object-fit: cover;
}
object-fit: cover matters here. Clipping does not resize anything; it only decides what is painted. Without object-fit the image keeps its intrinsic ratio inside the box and the blob clips whatever happens to be there, usually off-center.
Clipping a card
A card is the same rule on a container, with one extra consideration: the clip hides content, it does not reflow it.
<article class="blob card">
<h3>Field notes</h3>
<p>Six weeks of measurements from the north slope.</p>
</article>
.card {
background: #1e8fe5;
color: #fff;
padding: 4rem 3rem;
aspect-ratio: 1;
display: grid;
align-content: center;
text-align: center;
}
The padding is doing real work. Text near the edge of a blob-clipped card gets cut off, and the narrowest part of the outline sets the safe width. Test at your smallest breakpoint, where the text wraps to the most lines and is therefore tallest.
What happens to hover and clicks
A clipped element only responds inside what remains. Pointer events do not reach the clipped-away regions, so :hover does not fire in the corners of a blob-clipped button, and a click there passes through to whatever is behind.
Most of the time that is the behaviour you want, because the hit area now matches what people can see. It becomes a problem when a large decorative blob sits over content: the visible part still swallows clicks meant for the link beneath it. Give decorative shapes pointer-events: none and the problem disappears.
.decoration {
position: absolute;
inset: 0;
pointer-events: none;
}
The last thing clip-path does not do is change layout. The element still occupies its full rectangle, and text beside it runs along that rectangle rather than following the curve. Flowing text around the outline is shape-outside, a separate property that takes the same basic shapes and applies them to a float. Give a floated element the same shape() value in both properties and the clip and the text flow will agree.