www 101

All you need to know about the internet

Have a Question?

If you have any question you can ask below or enter what you are looking for!

How to Build a Semi-Circle Donut Chart With CSS

Although HTML5 Canvas and SVG might be more elegant solutions for building charts, in this tutorial we’ll learn how to build our very own donut chart with nothing but plain CSS.

To get an idea of what we’ll be creating, have a look at the embedded CodePen demo below:

HTML Markup

We start with some very basic markup; a plain unordered list with a span element inside each of the list items: 

Adding Styles to the List

With the markup ready, first we apply some basic styles to the unordered list:

Then, we’re going to give each one an ::after and a ::before pseudo-element, and style them:

Pay attention to the styles for the ::before pseudo-element. This gives us our half circle.

pseudo elements
Pseudo elements

So far, the aforementioned rules give us this result:

Adding Styles to the List Items

Let’s now discuss the styling of the list items.


With regards to the list items’ position, we do the following:

  • Position them right underneath their parent and
  • give them appropriate styles so as to create a reverse half circle.

Furthermore, a couple of things are worth noting here:

  • The list items are absolutely positioned, thus we’re able to set their z-index property.
  • We modify the default transform-origin property value (i.e. transform-origin: 50% 50%) of the list items. Specifically, we set transform-origin: 50% 0. In this way, when we animate (rotate) the items, their center top corner will become the center of rotation.

Here are the associated CSS styles:

Take a look at what we’ve built so far in the next visualization:

spans and list items

Currently, the only list item which is visible is the green one (which has z-index: 4;) the others are underneath it.


Before we cover the steps for animating our list items, let’s take note of the desired percentage for each item (ie: how much of the donut each will cover). Consider the following table:

Language Percentage
CSS 12
PHP 34
Python 22

Next, we calculate how many degrees we have to animate (rotate) each of the items. To find out the exact number of degrees for each item, we multiply its percentage by 180° (not 360° because we’re using a semi-circle donut chart):

Language Percentage Number of Degrees
CSS 12 12/100 * 180 = 21.6
HTML 32 32/100 * 180 = 57.6
PHP 34 34/100 * 180 = 61.2
Python 22 22/100 * 180 = 39.6

At this point we’re ready to set up the animations. First, we define some animation styles that are shared across all items, by adding some rules to .chart-skills li:

Then, we define the unique animation styles:

Notice that we add a delay to all items except for the first one. In this way, we create nice sequential animations. For example, when the animation of the first element finishes, the second element appears, and so on. 

The next step is to specify the actual animations:

Before going any further, we’ll briefly look at how the animations work:

The first element goes from transform: none to transform: rotate(21.6deg).

The second element goes from transform: rotate(21.6deg)  (starts from the final position of the first element) to transform: rotate(79.2deg) (57.6deg + 21.6deg). 

The third element goes from transform: rotate(79.2deg)  (starts from the final position of the second element) to transform: rotate(140.4deg) (61.2deg + 79.2deg).

The fourth element goes from transform: rotate(140.4deg)  (starts from the final position of the third element) to transform: rotate(180deg) (140.4deg + 39.6deg).


Last but not least, to hide the bottom half of the chart, we have to add the following rules:

The overflow: hidden property value ensures that only the first semi-circle (the one created with the ::before pseudo-element) is visible. Feel free to remove that property if you want to test the initial position of the list items. 

The transform-style: preserve-3d and backface-visibility: hidden properties prevent flickering effects that may occur in different browsers due to animations. If this problem still exists in your browser, you may want to try these solutions as well.

The chart is almost ready! All that remains is to style the chart labels, which we’ll do in the next section.

Here’s the CodePen demo showing the current appearance of our chart:

Adding Styles to the Labels

In this section, we’ll style the chart labels.


With regards to their position, we do the following:

  • Give them position: absolute and use the top and left properties to set their desired position.
  • Use negative values to rotate them. Of course, these aren’t random values. In fact, these are extracted from the last frame of their parent item. For instance, the last frame of the second list item includes transform: rotate(79.2deg), and thus its related label will have transform: rotate(-79.2deg).

Below are the corresponding CSS styles:


Now that we’ve positioned the labels, it’s time to animate them. Two things are worth mentioning here:

  • By default, all labels are hidden and become visible as their parent item is being animated. 
  • Similarly to the parent items, we use the animation-delay property to create sequential animations. In addition, we add the backface-visibility: hidden property value to ensure that there aren’t any flickering effects due to animations.

The CSS rules that deal with the animation of the chart labels are shown below:

Here’s the final chart:

Browser Support & Issues

In general, the demo works well in all browsers. I just want to discuss two small issues that are related to the border-radius property.

First, if we were to give different colors to our items, the chart might look something like this: 

for example the top and bottom corners of the third item. There are two
red lines which come from the border color of the fourth item. We can
see those lines because the fourth item has a darker border color
compared to the third one. Although this is a small issue, it’s good to be aware of it in
order to choose appropriate colors for your own charts.

Secondly, in Safari the chart appears as follows:

Look at the small gaps appearing in the second and third items. If you know anything regarding this issue, let us know in the comments below!


In this tutorial, we went through the process of creating a semi-circle donut chart with pure CSS. Again, as mentioned in the introduction, there are potentially more powerful solutions (e.g. HTML5 Canvas and SVG) out there for creating these kind of things. However, if you want to build something simple and lightweight, and enjoy a challenge, CSS is the way to go!