Semicircles in CSS are more trickier than you think!
Tags: webdev, css, frontend, learning
Published: 2024-01-19 18:34:24

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:

{% codepen https://codepen.io/ahmadkdev/pen/dyrNBpG default-tab=css,result theme-id=dark %}

## 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:

{% codepen https://codepen.io/ahmadkdev/pen/jOJmPow default-tab=css,result theme-id=dark %}

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

{% codepen https://codepen.io/ahmadkdev/pen/KKEmdPX default-tab=css,result theme-id=dark %}

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:

{% codepen https://codepen.io/ahmadkdev/pen/zYbwqQL default-tab=css,result theme-id=dark %}

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](https://www.w3.org/TR/css-backgrounds-3/#corner-overlap) 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(_L<sub>i</sub>_/_S<sub>i</sub>_), where _i_ ∈ {top, right, bottom, left}, _S<sub>i</sub>_ is the sum of the two corresponding radii of the corners on side _i_, and _L<sub>top</sub>_ = _L<sub>bottom</sub>_ = the width of the box, and _L<sub>left</sub>_ = _L<sub>right</sub>_ = 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. _L<sub>top</sub>_ / _S<sub>top</sub>_ = 600 / (16 + 999) = 0.5911330049261

2. _L<sub>bottom</sub>_ / _S<sub>bottom</sub>_ = 600 / (16 + 999) = 0.5911330049261

3. _L<sub>left</sub>_ / _S<sub>left</sub>_ = 350 / (16 + 16) = 18.75

4. _L<sub>right</sub>_ / _S<sub>right</sub>_ = 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

{% codepen https://codepen.io/ahmadkdev/pen/gOELjbG default-tab=css,result theme-id=dark %}

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
- [Border-radius in percentage (%) and pixels (px) or em](https://stackoverflow.com/q/29966499)
- [Overlapping Curves](https://www.w3.org/TR/css-backgrounds-3/#corner-overlap)