Mastering Modern CSS Shapes with shape()
Creating CSS shapes has always been a fun and creative challenge — and one of my favorite exercises as a developer. Over the years, I’ve built up one of the most comprehensive collections of CSS shapes, available at css-shape.com, where you can easily copy and use any shape you like. I’ve also written a detailed guide, The Modern Guide For Making CSS Shapes, that explores many of the techniques used to create them.
While that guide covers most modern approaches, CSS continues to evolve. One of the most exciting recent developments is the addition of the shape() function to clip-path — a real game changer for working with CSS shapes.
Note: As of May 2025, shape() is supported in Chrome 137+ and Safari 18.4+.
What Is shape()?
According to the official CSS Shapes Level 2 specification:
While path() lets us reuse SVG path syntax to define complex shapes, it comes with limitations — such as requiring the path to be a single string, and only supporting px units. The shape() function offers similar capabilities, but with standard CSS syntax and full access to features like var(), calc(), and multiple units.
In short, shape() brings the power of SVG to native CSS in a more flexible way. It’s essentially a superset of path(): anything you can do with path() can be done with shape(), and more.
To make things easier, I’ve even created a handy SVG-to-CSS converter at css-generators.com/svg-to-css/. If you already have an SVG path, just paste it into the tool to get the equivalent shape() CSS code, ready to use and customize.
For example, here’s how I converted the CSS-Tricks logo from SVG to CSS using the tool:
SVG Input:
After pasting the path into the converter, you get this shape() CSS:
.shape {
aspect-ratio: 0.933;
clip-path: shape(from 43.18% 61.52%, line by -24.35% 16.67%, curve by -8.12% 3.03% with -2.92% 1.82%/-5.2% 3.03%, …);
}
The converter automatically determines the appropriate bounding box — no need to worry about viewBox values or overflow issues.
When Should You Use shape()?
While shape() is powerful, it’s not always the best tool for the job. In my CSS Shapes guide, I categorize shapes based on two factors:
– Straight lines vs. curves
– Repetition vs. no repetition
This results in four shape types. Here’s how to choose the right method:
– Straight lines, no repetition → Use clip-path: polygon()
– Repetition (with or without curves) → Use mask and gradients
– Curves, no repetition → Use clip-path: shape()
Of course, these are general guidelines. You’ll need to consider factors like code complexity, browser support, and flexibility on a case-by-case basis.
Let’s Draw Some Shapes!
Rather than dive deep into the shape() syntax, let’s learn by doing. We’ll create a few common shapes using shape() to get comfortable with its commands and capabilities.
1. Rectangle
Starting with a basic rectangle:
clip-path: polygon(
0 0,
100% 0,
100% 100%,
0 100%
);
Using shape():
clip-path: shape(
from 0 0,
line to 100% 0,
line to 100% 100%,
line to 0 100%
);
You can simplify further with hline and vline:
clip-path: shape(
from 0 0,
hline to 100%,
vline to 100%,
hline to 0
);
Tip: Start with line and optimize later with hline/vline if needed.
2. Circular Cut-Out
Let’s add a half-circle cut-out to the top of our rectangle using arc:
clip-path: shape(
from 0 0,
hline to calc(50% – var(–r)),
arc by calc(2 * var(–r)) 0 of 1px,
hline to 100%,
vline to 100%,
hline to 0
);
Here, var(–r) defines the radius. Using arc by instead of arc to lets us define relative movement from the previous point.
Why use of 1px instead of the actual radius? When the radius is too small to draw the arc
Discover more from WIREDGORILLA
Subscribe to get the latest posts sent to your email.