Rounded Corners for Complex Shapes With the CSS Paint API
Rounding the corners of an arbitrary polygon is a problem CSS has never answered cleanly. The usual workarounds live in SVG: either hand-author long path() strings or reach for a filter with side effects. The CSS Paint API offers a third route, one where the shape and its radius stay as plain CSS custom properties and a paint() worklet does the geometry.
The approach follows the same pattern used in earlier parts of this series: keep a simple CSS declaration for the shape, then let the worklet handle everything else. A demo environment is the only caveat — this API currently runs in Chrome and Edge only.
Why Not the Usual Tricks?
Three existing techniques were the baseline before the Paint API version:
clip-path: path()— accepts an SVG path directly, but adjusting curvature or dimension means editing an opaque string of coordinates.- SVG filter on a
clip-pathshape — applies a gooey filter to round corners; the radius maps tostdDeviation, but fine control demands SVG expertise. - Ana Tudor's CSS-only gooey technique — uses a mix of
filterandmix-blend-mode; clever but brittle when transparency or images enter the layout.
None of those gives the adjustable, maintainable experience of a CSS variable. That's the gap the Paint API fills.
Building the Rounded Mask
The construction follows the polygon-border worklet from the previous article. The shape is declared as a --path variable in the same syntax a clip-path: polygon() expects; a second --radius variable sets the curvature.
.element {
--path: 50% 0, 100% 100%, 0 100%;
--radius: 20px;
-webkit-mask: paint(rounded-mask);
mask: paint(rounded-mask);
}
On the JavaScript side, the worklet reads the path's points (the red anchors) and inserts a midpoint between each consecutive pair (the green anchors). Each corner then has three points: a red one with one green on each side. The canvas arcTo() method draws the arc that connects the two segments those points imply, automatically smoothing the sharp corner. As MDN notes, the arc is tangential to two imaginary straight segments that would otherwise form a sharp corner.
The result is a rounded path that can be filled and used as a mask on any element. Because the radius is a CSS custom property, it's even animatable with @property.
Two Drawbacks Worth Knowing
First, the mask leaves the original rectangular box interactive. The hover area still covers the full element even where the mask is transparent. Applying clip-path shrinks that zone but clips the inward-facing parts of the curvature, so each rounded corner loses some of its arc.
Second, an unreasonably large --radius makes the geometry collapse into odd shapes. The fix caps the effective radius per corner at the largest value that still fits the segment lengths. The code computes that maximum from the two segment lengths meeting at each corner and clamps the requested radius accordingly. Regular polygons, for example, reach a circle at that cap, which is useful for shape transitions.
Borders, Dashes, and Per-Corner Control
Getting a stroked outline instead of a fill only requires swapping from fill() to stroke(). But unlike a fill, the stroke is centered on the path: half the line sits outside the shape and bleeds past the mask. The fix is to stack two masks. One paints the original filled shape on the main element; the other paints the stroke on a pseudo-element with the line width set to twice the desired thickness. The fill mask trims the outer half of the stroke, leaving a clean border.
The worklet also gains a conditional variable to select between drawing the interior or just the border. Dashes come along for free with the canvas method setLineDash(), as introduced in the previous article.
Individual corners can take their own radius. The --path syntax extends from a list of [x y] points to [x y r], where the third entry is optional. If a corner omits it, that corner falls back to the value of --radius:
const radius = points.map((p) => {
const parts = p.trim().split(/\s+/).map(parseFloat);
return parts[2] || this.r;
});
Putting It to Use
The worklet opens up several practical patterns:
- CSS shapes — rounded polygons, stars, and other forms without extra markup (see Example 1 in the original article's set).
- Speech bubbles — the border worklet from an earlier tutorial now gains rounded corners with minimal CSS changes.
- Frames — custom, responsive borders with any gradient background, no image assets needed.
- Section dividers — wavy transitions between sections no longer require inline SVG.
- Navigation menus — inverted radii for notch-style links are achievable by adjusting the path points.
- Gooey animations — animating a single path point triggers fun, interconnected effects.
- Shape morphing — oversizing the radius against a regular polygon yields a smooth circle; animating the stroke gives "breathing" forms.
The CSS Paint API won't replace every SVG element, but for polygon-heavy, adjustable visual components it offers a cleaner development surface than hand-crafted paths or filter hacks — once browser support reaches beyond the Chromium family. Until then, it remains a tool for progressive enhancement and for projects where the engineering trade-offs justify the restriction.



