Including borders to advanced shapes is a ache, however rounding the nook of advanced shapes is a nightmare! Fortunately, the CSS Paint API is right here to the rescue! That’s what we’re going to have a look at as a part of this “Exploring the CSS Paint API” collection.

Exploring the CSS Paint API collection:

Right here’s what we’re aiming for. Like all the pieces else we’ve checked out on this collection, observe that solely Chrome and Edge assist this for now.

Stay Demo

You’ll have seen a sample forming should you’ve adopted together with the remainder of the articles. On the whole, after we work with the CSS Paint API:

  • We write some fundamental CSS that we are able to simply modify.
  • All of the advanced logic is completed behind the scene contained in the paint() perform.

We will truly do that with out the Paint API

There are most likely loads of methods to place rounded corners on advanced shapes, however I’ll share with you three strategies I’ve utilized in my very own work.

I already hear you saying: If you happen to already know three strategies, then why are you utilizing the Paint API? Good query. I’m utilizing it as a result of the three strategies I’m conscious of are troublesome, and two of them are particularly associated to SVG. I’ve nothing in opposition to SVG, however a CSS-only resolution makes factor simpler to take care of, plus it’s simpler for somebody to stroll into and perceive when grokking the code.

Onto these three strategies…

Utilizing clip-path: path()

In case you are an SVG guru, this methodology is for you. the clip-path property accepts SVG paths. Meaning we are able to simply cross within the path for a fancy rounded form and be achieved. This method is tremendous simple if you have already got the form you need, however it’s unsuitable in order for you an adjustable form the place, for instance, you need to modify the radius.

Under an instance of a rounded hexagon form. Good luck making an attempt to regulate the curvature and the form measurement! You’re gonna need to edit that crazy-looking path to do it.

I suppose you may seek advice from this illustrated information to SVG paths that Chris put collectively. But it surely’s nonetheless going to be loads of work to plot the factors and curves simply the way you need it, even referencing that information.

Utilizing an SVG filter

I found this method from Lucas Bebber’s submit about making a gooey impact. You could find all of the technical particulars there, however the concept is to use an SVG filter to any component to spherical its corners.

We merely use clip-path to create the form we wish then apply the SVG filter on a guardian component. To regulate the radius, we modify the stdDeviation variable.

This can be a good method, however once more, it requires a deep stage of SVG know-how to make changes on the spot.

Utilizing Ana Tudor’s CSS-only method

Sure, Ana Tudor discovered a CSS-only method for a gooey impact that we are able to use to not far away of advanced shapes. She’s most likely writing an article about it proper now. Till then, you may seek advice from the slides she made the place she clarify the way it works.

Under a demo the place I’m changing the SVG filter together with her method:

Once more, one other neat trick! However so far as being simple to work with? Not a lot right here, both, particularly if we’re contemplating extra advanced conditions the place we’d like transparency, pictures, and so on. It’s work discovering the proper mixture of filter, mix-blend-mode and different properties to get issues excellent.

Utilizing the CSS Paint API as an alternative

Until you’ve a killer CSS-only option to put rounded borders on advanced shapes that you just’re preserving from me (share it already!), you may most likely see why I made a decision to achieve for the CSS Paint API.

The logic behind this depends on the identical code construction I used within the article overlaying the polygon border. I’m utilizing the --path variable that defines our form, the cc() perform to transform our factors, and some different methods we’ll cowl alongside the way in which. I extremely suggest studying that article to higher perceive what we’re doing right here.

First, the CSS setup

We first begin with a basic rectangular component and outline our form contained in the --path variable (form 2 above). The --path variable behaves the identical means as the trail we outline inside clip-path: polygon(). Use Clippy to generate it. 

.field {
  show: inline-block;
  top: 200px;
  width: 200px;

  --path: 50% 0,100% 100%,0 100%;
  --radius: 20px;
  -webkit-mask: paint(rounded-shape);

Nothing advanced to this point. We apply the customized masks and we outline each the --path and a --radius variable. The latter shall be used to regulate the curvature.

Subsequent, the JavaScript setup

Along with the factors outlined by the trail variable (pictured as pink factors above), we’re including much more factors (pictured as inexperienced factors above) which are merely the midpoints of every section of the form. Then we use the arcTo() perform to construct the ultimate form (form 4 above).

Including the midpoints is fairly simple, however utilizing arcTo() is a bit difficult as a result of we have now to know the way it works. Based on MDN:

[It] provides a round arc to the present sub-path, utilizing the given management factors and radius. The arc is robotically linked to the trail’s newest level with a straight line, if mandatory for the desired parameters.

This methodology is usually used for making rounded corners.

The truth that this methodology requires management factors is the primary purpose for the additional midpoints factors. It additionally require a radius (which we’re defining as a variable known as --radius).

If we proceed studying MDN’s documentation:

A method to consider arcTo() is to think about two straight segments: one from the place to begin to a primary management level, and one other from there to a second management level. With out arcTo(), these two segments would kind a pointy nook: arcTo() creates a round arc that matches this nook and smooths is out. In different phrases, the arc is tangential to each segments.

Every arc/nook is constructed utilizing three factors. If you happen to test the determine above, discover that for every nook we have now one pink level and two inexperienced factors on all sides. Every red-green mixture creates one section to get the 2 segments detailed above.

Let’s zoom into one nook to higher perceive what is going on:

We now have each segments illustrated in black.
The circle in blue illustrates the radius.

Now think about that we have now a path that goes from the primary inexperienced level to the following inexperienced level, shifting round that circle. We do that for every nook and we have now our rounded form.

Right here’s how that appears in code:

// We first learn the variables for the trail and the radius.
const factors = properties.get('--path').toString().break up(',');
const r = parseFloat(properties.get('--radius').worth);

var Ppoints = [];
var Cpoints = [];
const w = measurement.width;
const h =;
var N = factors.size;
var i;
// Then we loop by means of the factors to create two arrays.
for (i = 0; i < N; i++) {
  var j = i-1;
  if(j<0) j=N-1;
  var p = factors[i].trim().break up(/(?!(.*)s(?![^(]*?))/g);
  // One defines the pink factors (Ppoints)
  p = cc(p[0],p[1]);
  var pj = factors[j].trim().break up(/(?!(.*)s(?![^(]*?))/g);
  pj = cc(pj[0],pj[1]);
  // The opposite defines the inexperienced factors (Cpoints)

/* ... */

// Utilizing the arcTo() perform to create the form
for (i = 0; i < (Cpoints.size - 1); i++) {
  ctx.arcTo(Ppoints[i][0], Ppoints[i][1], Cpoints[i+1][0],Cpoints[i+1][1], r);
ctx.arcTo(Ppoints[i][0], Ppoints[i][1], Cpoints[0][0],Cpoints[0][1], r);

/* ... */


The final step is to fill our form with a strong coloration. Now we have now our rounded form and we are able to use it as a masks on any component.

That’s it! Now all we have now to do is to construct our form and management the radius like we wish — a radius that we are able to animate, due to @property which is able to make issues extra fascinating!

Stay Demo

Are there any drawbacks with this methodology?

Sure, there are drawbacks, and also you most likely seen them within the final instance. The primary downside is expounded to the hover-able space. Since we’re utilizing masks, we are able to nonetheless work together with the preliminary rectangular form. Bear in mind, we confronted the identical problem with the polygon border and we used clip-path to repair it. Sadly, clip-path doesn’t assist right here as a result of it additionally impacts the rounded nook.

Let’s take the final instance and add clip-path. Discover how we’re dropping the “inward” curvature.

There’s no problem with the hexagon and triangle shapes, however the others are lacking some curves. It could possibly be an fascinating function to maintain solely the outward curvature — due to clip-path— and on the similar time we repair the hover-able space. However we can’t maintain all of the curvatures and scale back the hover-able space on the similar time.

The second problem? It’s associated to the usage of an enormous radius worth. Hover over the shapes beneath and see the loopy outcomes we get:

It’s truly not a “main” downside since we have now management over the radius, however it positive can be good to keep away from such a scenario in case we wrongly use an excessively giant radius worth. We may repair this by limiting the worth of the radius to inside a spread that caps it at a most worth. For every nook, we calculate the radius that permits us to have the most important arc with none overflow. I received’t dig into the maths logic behind this (😱), however right here is the ultimate code to cap the radius worth:

var angle = 
Math.atan2(Cpoints[i+1][1] - Ppoints[i][1], Cpoints[i+1][0] - Ppoints[i][0]) -
Math.atan2(Cpoints[i][1]   - Ppoints[i][1], Cpoints[i][0]   - Ppoints[i][0]);
if (angle < 0) {
  angle += (2*Math.PI)
if (angle > Math.PI) {
  angle = 2*Math.PI - angle
var distance = Math.min(
    (Cpoints[i+1][1] - Ppoints[i][1]) ** 2 + 
    (Cpoints[i+1][0] - Ppoints[i][0]) ** 2),
    (Cpoints[i][1] - Ppoints[i][1]) ** 2 + 
    (Cpoints[i][0] - Ppoints[i][0]) ** 2)
var rr = Math.min(distance * Math.tan(angle/2),r);

r is the radius we’re defining and rr is the radius we’re truly utilizing. It equal both to r or the utmost worth allowed with out overflow.

If you happen to hover the shapes in that demo, we not get unusual shapes however the “most rounded form” (I simply coined this) as an alternative. Discover that the common polygons (just like the triangle and hexagon) logically have a circle as their “most rounded form” so we are able to have cool transitions or animations between totally different shapes.

Can we have now borders?

Sure! All we have now to do is to make use of stroke() as an alternative of fill() inside our paint() perform. So, as an alternative of utilizing:


…we use this:

ctx.lineWidth = b;

This introduces one other variable, b, that controls the border’s thickness.

Did you discover that we have now some unusual overflow? We confronted the identical problem within the earlier article, and that resulting from how stroke() works. I quoted MDN in that article and can do it once more right here as properly:

Strokes are aligned to the middle of a path; in different phrases, half of the stroke is drawn on the inside facet, and half on the outer facet.

Once more, it’s that “half inside facet, half outer facet” that’s getting us! In an effort to repair it, we have to cover the outer facet utilizing one other masks, the primary one the place we use the fill(). First, we have to introduce a conditional variable to the paint() perform in an effort to select if we need to draw the form or solely its border.

Right here’s what we have now:

if(t==0) {
} else {
  ctx.lineWidth = 2*b;

Subsequent, we apply the primary sort of masks (t=0) on the primary component, and the second sort (t=1) on a pseudo-element. The masks utilized on the pseudo-element produces the border (the one with the overflow problem). The masks utilized on the primary component addresses the overflow problem by hiding the outer a part of the border. And should you’re questioning, that’s why we’re including twice the border thickness to lineWidth.

Stay Demo

See that? We now have good rounded shapes as outlines and we are able to modify the radius on hover. And may use any sort of background on the form.

And we did all of it with a little bit of CSS:

div {
  --radius: 5px; /* Defines the radius */
  --border: 6px; /* Defines the border thickness */
  --path: /* Outline your form right here */;
  --t: 0; /* The primary masks on the primary component */
  -webkit-mask: paint(rounded-shape);
  transition: --radius 1s;
div::earlier than {
  content material: "";
   background: ..; /* Use any background you need */
  --t: 1; /* The second masks on the pseudo-element */
  -webkit-mask: paint(rounded-shape); /* Take away this in order for you the complete form */
div[class]:hover {
  --radius: 80px; /* Transition on hover */

Let’s not overlook that we are able to simply introduce dashes utilizing setLineDash() the identical means we did within the earlier article.

Stay Demo

Controlling the radius

In all of the examples we’ve checked out, we at all times think about one radius utilized to all of the corners of every form. It might be fascinating if we may management the radius of every nook individually, the identical means the border-radius property takes as much as 4 values. So let’s lengthen the --path variable to think about extra parameters.

Really, our path might be expressed as an inventory of [x y] values. We’ll make an inventory of [x y r] values the place we introduce a 3rd worth for the radius. This worth isn’t necessary; if omitted, it falls again to the primary radius.

.field {
  show: inline-block;
  top: 200px;
  width: 200px;

  --path: 50% 0 10px,100% 100% 5px,0 100%;
  --radius: 20px;
  -webkit-mask: paint(rounded-shape);

Above, we have now a 10px radius for the primary nook, 5px for the second, and since we didn’t specify a price for the third nook, it inherits the 20px outlined by the --radius variable.

Right here’s our JavaScript for the values:

var Radius = [];
// ...
var p = factors[i].trim().break up(/(?!(.*)s(?![^(]*?))/g);

This defines an array that shops the radius of every nook. Then, after splitting the worth of every level, we take a look at whether or not we have now a 3rd worth (p[2]). If it’s outlined, we use it; if not, we use the default radius. In a while, we’re utilizing Radius[i] as an alternative of r.

Stay Demo

This minor addition is a pleasant function for after we need to disable the radius for a selected nook of the form. Actually, let’s take a look at just a few totally different examples subsequent.

Extra examples!

I made a collection of demos utilizing this trick. I like to recommend setting the radius to 0 to higher see the form and perceive how the trail is created. Keep in mind that the --path variable behaves the identical means as the trail we outline inside clip-path: polygon(). If you happen to’re searching for a path to play with, attempt utilizing Clippy to generate one for you.

Instance 1: CSS shapes

A variety of fancy shapes might be created utilizing this method. Listed below are just a few of them achieved with none additional components, pseudo-elements, or hack-y code.

Instance 2: Speech bubble

Within the earlier article, we added border to a speech bubble component. Now we are able to enhance it and around the corners utilizing this new methodology.

If you happen to examine with this instance with the unique implementation, you could discover the very same code. I merely made two or three modifications to the CSS to make use of the brand new Worklet.

Instance 3: Frames

Discover beneath some cool frames to your content material. No extra complications after we want gradient borders!

Merely play with the --path variable to create your personal responsive body with any coloration your need.

Instance 4: Part divider

SVG is not wanted to create these wavy part dividers which are standard today.

Discover that the CSS is gentle and comparatively easy. I solely up to date the trail to generate new situations of the divider.

Right here’s a basic design sample that I’m positive many people have ran into at a while: How on earth can we invert the radius? You’ve possible seen it in navigation designs.

A barely totally different tackle it:

Instance 6: Gooey impact

If we play with the trail values we are able to attain for some fancy animation.
Under an concept the place I’m making use of a transition to just one worth of the trail and but we get a reasonably cool impact

This one’s impressed by Ana Tudor’s demo.

One other concept with a distinct animation

One other instance with a extra advanced animation:

What a few bouncing ball

Instance 7: Form morphing

Taking part in with large radius values permits us to create cool transitions between totally different shapes, particularly between a circle and an everyday polygon.

If we add some border animation, we get “respiratory” shapes!

Let’s spherical this factor up

I hope you’ve loved getting nerdy with the CSS Paint API. All through this collection, we’ve utilized paint() to a bunch of real-life examples the place having the API permits us to control components in a means we’ve by no means been in a position to do with CSS — or with out resorting to hacks or loopy magic numbers and whatnot. I really imagine the CSS Paint API makes seemingly sophisticated issues quite a bit simpler to unravel in a simple means and shall be a function we attain for again and again. That’s, when browser assist catches as much as it.

If you happen to’ve adopted together with this collection, and even simply stumbled into this one article, I’d like to know what you consider the CSS Paint API and the way you think about utilizing it in your work. Are there any present design tendencies that might profit from it, just like the wavy part dividers? Or blobby designs? Experiment and have enjoyable!

This one’s taken from my earlier article

Exploring the CSS Paint API collection:

#Exploring #CSS #Paint #API #Rounding #Shapes

Leave a Reply

Your email address will not be published. Required fields are marked *