DEV Community

Cover image for Semicircles in CSS are more trickier than you think!
Ahmad Khalid
Ahmad Khalid

Posted on

Semicircles in CSS are more trickier than you think!

Semicircles or half circles are common in web design. And while they might seem simple to implement, they can be tricky sometimes. So I'll share my notes about them.

The first thing to know is that there's is no direct way in CSS to implement a semicircle e.x. we don't have something like top-left-radius: quarter-circle; or right-side-radius: semi-circle; so we need to figure things out ourselves to set the proper border-radius value and get the shape we desire.

And this can be simple or tricky depending on the box's dimensions i.e. width and height: whether they're equal or not and whether they're fixed or dynamic.

Let's explore three different boxes assuming we want a semicircle on the right side and a radius of 1rem on the left side:

1. A square box

The most basic box to deal with is a square box.
Our magic value here is 50%.
We give it to one corner and get a quarter circle.
We give it to two adjacent corners and get a half-circle.
We give it to the four corner and get a full circle.
and so on...

We can also mix it with other values and things would work perfectly as in this example:

2. A rectangle box with fixed dimensions

With rectangles things start to get more tricky.
Because width and height are not equal anymore, if we used 50% we'll get ourselves totally different result:

To get our desired result we need to use the value of the smallest dimension divided by 2

Well, we needed to do some math but it's still simple, that's because we know our box's dimensions in advance.

3. A rectangle with dynamic dimensions

Things get really complicated and tricky when we don't know the dimensions.

This is where the 999px trick kicks in, you might have seen it somewhere or even used it yourself. Let's see how it behaves then we'll explore how it works:

As you see in the snippet it's working and even when the box get resized it still gives us our desired perfect semicircle! You might also have noticed that even though the (999px) radii works well, the other radii (16px) is broken!

To understand why this happened we need to ask how the 999px trick actually works?

Well, It simply triggers the Overlapping Curves rule which states:

Corner curves must not overlap: When the sum of any two adjacent border radii exceeds the size of the border box, UAs must proportionally reduce the used values of all border radii until none of them overlap. The algorithm for reducing radii is as follows:
Let f = min(Li/Si), where i ∈ {top, right, bottom, left}, Si is the sum of the two corresponding radii of the corners on side i, and Ltop = Lbottom = the width of the box, and Lleft = Lright = the height of the box. If f < 1, then all corner radii are reduced by multiplying them by f.

To understand this formula assume a box with 600px width and 350px height and we give it border-radius: 16px 999px 999px 16px; the formula will go as follows:

  1. Ltop / Stop = 600 / (16 + 999) = 0.5911330049261

  2. Lbottom / Sbottom = 600 / (16 + 999) = 0.5911330049261

  3. Lleft / Sleft = 350 / (16 + 16) = 18.75

  4. Lright / Sright = 350 / (999 + 999) = 0.1751751751751

f = 0.1751751751751 (min result)

It's < 1 so all corner radii will be reduced by multiplying them by f
16px becomes 2.8028028028028px
999px becomes 175px

Now we know why it works and why it brokes the other radii as well.

We need a better solution then. Well, remember what was our real problem? We do not know the dimensions

So how can we know? Using CSS, we simply can't. We can use javascript but this is beyond our topic.

My solution using pure CSS

I created an element div.rad that takes its parent's full size. It's a flex container so I can easily size its pseudo-elements. And it's positioned absolutely with z-index: -1 so our main box can overlap it.
I used the ::after pseudo-element for the semicircle radius giving it a 100% height and aspect-ratio: 1 so it'll always be square.
Then I use the ::before pseudo-element for the 1rem radius giving it the remaining size but not less than 1rem so it won't disappear at some point and we lose the 1rem radius.

I was able to achieve both the 1rem radius and the semicircle radius in pure CSS.
Of course, this solution isn't perfect and I'd be happy to get your thoughts on my solution and the topic in general.
Thanks for reading and I hope it deserved your time.

Useful Resources

Top comments (0)