Creating Flower Shapes using clip-path: shape()
Creating Flower Shapes using clip-path: shape() ź“ė Ø
InĀ a previous article, we used modern CSS features such as mask
, trigonometric functions, and CSS variables to create flower-like shapes.

The HTML code was a single element, which means we can apply the CSS to image elements and get cool frames like the demo below:
In this article, we are redoing the same shapes using the new shape()
function, which I think will become my favorite CSS feature.
Note
At the time of writing, only Chrome, Edge, and Safari have the full support of the features we will be using.
What is shape()
?
You are probably familiar withĀ clip-path: polygon()
, right? A function that allows you to specify different points, draw straight lines between them and create various CSS shapes (I invite you to checkĀ my online collection of CSS shapesĀ to see some of them). I said āstraight linesā because when it comes to curves,Ā clip-path
Ā is very limited. We haveĀ circle()
Ā andĀ ellipse()
, but we cannot achieve complex shapes with them.
shape()
Ā is the new value that overcomes such limitation. In addition to straight lines, it allows us to draw curves. But If you check theĀ MDN pageĀ orĀ the specification, you can see that the syntax is a bit complex and not easy to grasp. Itās very similar to SVG path, which is good as it gives us a lot of options and flexibility, but it requires a lot of practice to get used to it.
I will not write a boring tutorial explaining the syntax and all the possible values, but rather focus on one command per article. In this article, we will study theĀ arc
Ā command, and the next article will be about theĀ curve
Ā command. And, of course, we are going to draw cool shapes. Otherwise itās no fun!
The arc
Command
This command allows you to draw an elliptic arc between two points but I will only consider the particular case of a circle which is easier and the one you will need the most. Letās start with some geometry to understand how it works.
If you have two points (A and B) and a radius, there are exactly two circles with the given radius that intersect with the points. The intersection of both circles gives us 4 possible arcs between A and B that we can identify with a size (small or large) and a direction (clockwise or counter-clockwise)

The code will look like the below:
clip-path: shape(from Xa Ya, arc to Xb Yb of R [large or small] [cw or ccw])
The first command of a shape is always aĀ from
Ā to give the starting point, and then we use theĀ arc
Ā to define the ending point, the radius, the size, and the direction.
Here is a demo to illustrate the different values:
The points and the radii are the same. I am only changing the size and direction to choose one of the four possibilities. It should be noted that the size and direction arenāt mandatory. The defaults areĀ small
Ā andĀ ccw
.
Thatās all: we have what we need to draw flower shapes!
Creating The Flower Shape
I will start with a figure from the previous article.

Using the mask method, we had to draw a big circle at the center and small circles placed around it. UsingĀ shape()
, we need to draw the arcs of the small circles. The starting and ending points of each arc are placed where the circles touch each other.

The code will look as follows:
.flower {
clip-path: shape(from X0 Y0,
arc to X1 Y1 of R,
arc to X2 Y2 of R,
arc to X3 Y3 of R,
arc to X4 Y4 of R,
...
arc to Xn Yn of R
)
}
And like I did with the mask method, I will rely on Sass to create a loop.
$n: 10;
.flower {
$m: ();
$m: append($m,from X0 Y0,comma);
@for $i from 1 through $n {
$m: append($m,arc to Xi Yi of R,comma);
}
clip-path: shape(#{$m});
}
Now we need to identify the radius of the small circles (R) and the position of the different points (Xi, Yi). I already did the calculation of the radius in the previous article, so I will reuse the same value:
R = 50%/(1 + 1/math.sin(180deg/$n))
For the points, they are placed around the same circle so their coordinate can be written like below:
Xi = 50% + D*math.cos($i*360deg/$n)
Yi = 50% + D*math.sin($i*360deg/$n)
Here is a figure to illustrate the distance D and the radius R:

I know you donāt want a boring geometry course so here is the value of D.
D = 50%/(math.tan(180deg/$n) + 1/math.cos(180deg/$n))
And the final code will be:
$n: 10;
.flower {
$m: ();
$r: 50%/(1 + 1/math.sin(180deg/$n));
$d: 50%/(math.tan(180deg/$n) + 1/math.cos(180deg/$n));
$m: append($m,from
50% + $d*math.cos(0)
50% + $d*math.sin(0),comma);
@for $i from 1 through $n {
$m: append($m,arc to
50% + $d*math.cos($i*360deg/$n)
50% + $d*math.sin($i*360deg/$n)
of $r,comma);
}
clip-path: shape(#{$m});
}
Wait, we get the inverted shape instead? Why is that?
We didnāt define the size and the direction of the arcs so by default the browser will useĀ small
Ā andĀ ccw
. This gives us the inverted version of the flower. If you try the 4 different combinations (including the default one) you will get the following:

small ccw
Ā andĀ large cw
Ā give us the shape we are looking for. TheĀ small cw
Ā is an interesting variation, and theĀ large ccw
Ā is a funny one. We can consider a CSS variable to easily control which shape we want to get.
Combining Both Shapes
Now, what about the shape below?

The idea is to use the same code and alternate between two arc configurations.
$n: 10;
.flower {
$m: ();
$r: 50%/(1 + 1/math.sin(180deg/$n));
$d: 50%/(math.tan(180deg/$n) + 1/math.cos(180deg/$n));
$m: append($m,from
50% + $d*math.cos(0)
50% + $d*math.sin(0),comma);
@for $i from 1 through $n {
$c:small ccw;
@if $i % 2 == 0 {$c:large cw}
$m: append($m,arc to
50% + $d*math.cos($i*360deg/$n)
50% + $d*math.sin($i*360deg/$n)
of $r $c,comma);
}
clip-path: shape(#{$m});
}
I introduced a new variableĀ $c
Ā within the loop that will have the valueĀ small ccw
Ā whenĀ $i
Ā is odd andĀ large cw
Ā otherwise.
Cool right? The compiled code will look like the below:
clip-path:
shape(from X0 Y0,
arc to X1 Y1 of R small ccw,
arc to X2 Y2 of R large cw,
arc to X3 Y3 of R small ccw,
arc to X4 Y4 of R large cw,
...
)
The first arc will use the inner curve (small ccw
), the next one the outer curve (large cw
) and so on. We repeat this to get our wavy-flower shape!
Optimizing The Code
We made a generic code that allow us to get any shape variation by simply controlling the size/direction of the arcs, but for each particular case, we can have a more simplified code.
For the inverted variation (small ccw
), the value ofĀ D
Ā can be replaced byĀ 50%
. This will simplify the formula and also increase the area covered by the shape. We also need to update the value of the radius.
$n: 10;
.flower {
$m: ();
$r: 50%*math.tan(180deg/$n);
$m: append($m,from
50% + 50%*math.cos(0)
50% + 50%*math.sin(0),comma);
@for $i from 1 through $n {
$m: append($m,arc to
50% + 50%*math.cos($i*360deg/$n)
50% + 50%*math.sin($i*360deg/$n)
of $r,comma);
}
clip-path: shape(#{$m});
}
We can do the same for the main shape, but this time we can simplify the value of the radius and useĀ 1%
.
$n: 10;
.flower {
$m: ();
$d: 50%/(math.cos(180deg/$n)*(1 + math.tan(180deg/$n)));
$m: append($m,from
50% + $d*math.cos(0)
50% + $d*math.sin(0),comma);
@for $i from 1 through $n {
$m: append($m,arc to
50% + $d*math.cos($i*360deg/$n)
50% + $d*math.sin($i*360deg/$n)
of 1% cw,comma);
}
clip-path: shape(#{$m});
}
This one is interesting because usingĀ 1%
Ā as a radius is kind of strange and not intuitive. In the explanation of the arc command, I said that we have exactly two circles with the given radius that intersect with the two points, but what if the radius is smaller than half the distance between the points? No circles can meet that condition.
This case falls into an error handling where the browser will scale the radius until we can have at least one circle that meets the conditions (yes, itās defined withinĀ the specification). That circle will simply have its radius equal to half the distance between both points. It also means we only have two arcs with the same size (small
Ā andĀ large
Ā will be equal)

In other words, if you specify a small radius (likeĀ 1%
), you are telling the browser to find the radius value for you (a lazy but clever move!). This trick wonāt work in all the situations but can be handy in many of them so donāt forget about it.
Conclusion
We are done with this first article! You should have a good overview of the arc command and how to use it. I only studied the particular case of drawing circular arcs but once you get used to this you can move to the elliptic ones even if I think you will rarely need them.
Letās end with a last demo of a heart shape, where I am using the arc command. Can you figure out how to do it before checking my code?
And donāt forget to bookmarkĀ my online generatorsĀ from where you can get the code of the flower shapes and more!
Article Series