I first saw in the Safari 18.4 release notes that shape()
, a new function is now supported. Then I saw on MDN it’s actually already in Chrome, too!
The shape()
function joins friends like polygon()
, circle()
, rect()
, inset()
, and a handful of others. These functions are used as values for a handful of things in CSS, namely:
clip-path
— Clipping away parts of elementsoffset-path
— Moving elements along a pathshape-outside
— Applied to afloat
-ed element such that content flows along the path
Fair warning: shape()
only seems to work with clip-path
. I couldn’t find a ton of information on this, but the Chrome blog does state it. It will probably work with the other properties in due time.
Let’s focus on clip-path
here which I might argue is the most useful anyway, as it makes an entire element into the shape described which feels like a more generally applicable thing.
I got into this on the CodePen blog where I equated shape()
to <path d="">
in SVG, which is surely the intention. You can actually set the d
attribute in CSS, but it only works on <path>
elements, and the unitless values translate only to pixels, which makes it not particularly CSSy or useful.
One situation I mentioned was Trys Mudford’s blog post where this was the design situation at hand:

Those light yellow boxes are basically polygons with rounded corners. In a perfect world, polygon()
could do this with the round
keyword, as specced, but alas that doesn’t work just yet. But because shape()
is essentially all-powerful, that does work now (in Chrome and Safari anyway, and this feels like a decently progressive-enhancement thing).
Temani Afif saw that and did the work!
This is very awesome. This is quite the power tool for shape-making. I think we’re going to see a lot of fancy stuff come out of this.
It’s true we already have a path()
function, but remember, it’s sooooo limited. The values are only pixels, which are some pretty big handcuffs in a responsive world full of intrinsic content (that is, elements on the web that respond to their contents and environment). Simon Fraser on the WebKit blog introduces this new feature and calls it out:
… using
path()
inclip-path
can’t be responsive; you can’t write CSS rules so that the path adapts to the size of the element. This is where the newshape()
function comes in.
Coincidentally, Simon’s demo (Jen’s demo?) also shows off an arrow shape:
That’s using multiple different drawing commands (line
and arc
, but there are more), keywords like top
and left
(excellent, but I wonder why logical properties don’t work?), and, even more deliciously, container units (e.g. cqh
). The orange border there is a good reminder that clip-path
, well, clips. So it’ll lop off anything at all on this element in those areas, including content.
Noam Rosenthal got in on the fun over on the Chrome for Developers blog, underscoring just how hard this stuff used to be:
clip-path: shape()
lets you clip your element using arbitrary and responsive shapes, previously only possible using techniques like conic gradients or JavaScript-constructed SVG.
And like all this good company, absolutely couldn’t resist peppering in other CSS goodness into a demo. His demo here uses different drawing commands than we’ve seen so far, custom properties (which are an extremely natural fit), and even animation (!!).
I see Temani is hard on the case with a blob generator using shape()
, which, I believe as long as there are the “same number of points”, can be animated by changing the clip-path
entirely. Like:
And obviously I love this:
The Actual Shape Commands
The spec covers them, but the best writeup I’ve seen is Geoff’s on CSS-Tricks. He’s got a bit more detail so check that out, but here’s the list:
line
vline
hline
arc
curve
smooth
Each of them have a bit of sub-syntax to themselves. Like the curve
command might look like curve to 50% 50% with 50% 0
which would continue drawing the shape to the exact center of the element in a curve in which the top center is a “control point” and so curves in that direction.
In my experience it’s quite easy to make a small mistake in the syntax and wreck the whole thing. But hey that’s understandable.
shape()
Squircles with I get to have some fun too! It occurred to me that digital designs most elusive beast, the squircle, might be now achievable with reasonable normal web tech.
SVG can do it, but I wouldn’t call it particularly readable code. “Monoco is a tiny JavaScript library that adds squircles” (via SVG background images) and it does a pretty good job of it I’d say, but that’s more technology than I normally like to throw at something like this. Jared White by way of Simeon Griggs has a pretty nice SVG-based solution as well, leveraging SVG-as-clip-path.
I like how relatively chill that SVG path
is, but still, shape()
can allow us to squish this down into just CSS which is kinda sweet.
That is… if I was fully smart enough to do it.
I crudely drew one in Figma so that I could label the points for writing the syntax out.

I figured if I just did a curve
to every one of those points with control points a bit the edges, it would… work? So basically like this:
div {
clip-path: shape(
from 5% 3%,
curve to 95% 3% with 50% 0,
curve to 97% 5% with 97% 3%,
curve to 97% 95% with 100% 50%,
curve to 95% 97% with 97% 97%,
curve to 5% 97% with 50% 100%,
curve to 3% 95% with 3% 97%,
curve to 3% 5% with 0% 50%,
curve to 5% 3% with 3% 3%,
);
}
Code language: CSS (css)
Which basically works. I tried playing around with arc
and smooth
instead but couldn’t manage to make it any better (with my like zero geometry skills). Then instead of hard coding those percentage values, I made them in custom properties with sliders to squiggle them around a little.
It’s a little janky — but I trust someone make like a real quality geometrically sound version eventually.
Thanks for this post!
It’s definitely possible to create pseudo-squircles with shape(), however the corner-shape property we’re working on would enable those also in borders and shadows and not just as a clip-path.
(… also, offset-path: shape() works in Safari, and will work in Chrome starting version 137).
Ah nice!
I think I got it working, overriding a path() here when @support-ed
https://codepen.io/chriscoyier/pen/GggQrQq