PostHole
Compose Login
You are browsing us.zone2 in read-only mode. Log in to participate.
rss-bridge 2026-02-09T17:34:56+00:00

Trying to Make the Perfect Pie Chart in CSS

Can we make pie chart that's semantic, with flexible markup, and avoids using a JavaScript library? Here's how I tackled it.

Trying to Make the Perfect Pie Chart in CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.


charts data visualization

Trying to Make the Perfect Pie Chart in CSS

Juan Diego Rodríguez

Feb 9, 2026

Speaking of charts… When was the last time you had to use a pie chart? If you are one of those people who have to give presentations right and left, then congratulations! You are both in my personal hell… and also surrounded by pie charts. Luckily, I think I haven’t needed to use them in ages, or at least that was until recently.

Last year, I volunteered to make ta webpage for a kids’ charity in México1. Everything was pretty standard, but the staff wanted some data displayed as pie charts on their landing page. They didn’t give us a lot of time, so I admit I took the easy route and used one of the many JavaScript libraries out there for making charts.

It looked good, but deep down I felt dirty; pulling in a whole library for a couple of simple pie charts. Feels like the easy way out rather than crafting a real solution.

I want to amend that. In this article, we’ll try making the perfect pie chart in CSS. That means avoiding as much JavaScript as possible while addressing major headaches that comes with handwriting pie charts. But first, let’s set some goals that our “perfect” should comply with.

In order of priority:

  • This must be semantic! Meaning a screen reader should be able to understand the data shown in the pie chart.
  • This should be HTML-customizable! Once the CSS is done, we only have to change the markup to customize the pie chart.
  • This should keep JavaScript to a minimum! No problem with JavaScript in general, it’s just more fun this way.

Once we are done, we should get a pie chart like this one:

[A pie chart illustration in four segments differentiated by color. Each segment is labelled with a name and percentage.]

Is this too much to ask? Maybe, but we’ll try it anyways.

Conic gradients suck aren’t the best

We can’t talk about pie charts without talking first about conic gradients. If you’ve read anything related to the conic-gradient() function, then you’ve likely seen that they can be used to create simple pie charts in CSS. Heck, even I have said so in the almanac entry. Why not? If only with one element and a single line of CSS…

.gradient {
background: conic-gradient(blue 0% 12.5%, lightblue 12.5% 50%, navy 50% 100%);

We can have seemlessly perfect pie chart:

However, this method blatantly breaks our first goal of semantic pie charts. As it’s later noted on the same entry:

Do not use the conic-gradient() function to create a real pie chart, or any other infographics for that matter. They don’t hold any semantic meaning and should only be used decoratively.

Remember that gradients are images, so displaying a gradient as a background-image doesn’t tell screen readers anything about the pie charts themselves; they only see an empty element.

This also breaks our second rule of making pie charts HTML-customizable, since for each pie chart we’d have to change its corresponding CSS.

So should we ditch conic-gradient() altogether? As much as I’d like to, its syntax is too good to pass so let’s at least try to up its shortcomings and see where that takes us.

Improving semantics

The first and most dramatic problem with conic-gradient() is its semantics. We want a rich markup with all the data laid out so it can be understood by screen readers. I must admit I don’t know the best way to semantically write that, but after testing with NVDA, I believe this is a good enough markup for the task:

<figure>
<figcaption>Candies sold last month</figcaption>
<ul class="pie-chart">
<li data-percentage="35" data-color="#ff6666"><strong>Chocolates</strong></li>
<li data-percentage="25" data-color="#4fff66"><strong>Gummies</strong></li>
<li data-percentage="25" data-color="#66ffff"><strong>Hard Candy</strong></li>
<li data-percentage="15" data-color="#b366ff"><strong>Bubble Gum</strong></li>
</ul>
</figure>

Ideally, this is all we need for our pie chart, and once styles are done, just editing the data-* attributes or adding new <li> elements should update our pie chart.

Just one thing though: In its current state, the data-percentage attribute won’t be read out loud by screen readers, so we’ll have to append it to the end of each item as a pseudo-element. Just remember to add the “%” at the end so it also gets read:

.pie-chart li::after {
content: attr(data-percentage) "%";

So, is it accessible? It is, at least when testing in NVDA. Here it is in Windows:

You may have some questions regarding why I chose this or that. If you trust me, let’s keep going, but if not, here is my thought process:

Why use data-attributes instead of writing each percentage directly?

We could easily write them inside each <li>, but using attributes we can get each percentage on CSS through the attr() function. And as we’ll see later it makes working with CSS a whole lot easier.

Why <figure>?

The <figure> element can be used as a self-contained wrapper for our pie chart, and besides images, it’s used a lot for diagrams too. It comes in handy since we can give it a title inside <figcaption> and then write out the data on an unordered list, which I didn’t know was among the content permitted inside <figure> since <ul> is considered flow content.

Why not use ARIA attributes?

We could have used an aria-description attribute so screen readers can read the corresponding percentage for each item, which is arguably the most important part. However, we may need to visually show the legend, too. That means there is no advantage to having percentages both semantically and visually since they might get read twice: (1) once on the aria-description and (2) again on the pseudo-element.

Making it a pie chart

We have our data on paper. Now it’s time to make it look like an actual pie chart. My first thought was, “This should be easy, with the markup done, we can now use a conic-gradient()!”

Well… I was very wrong, but not because of semantics, but how the CSS Cascade works.

Let’s peek again at the conic-gradient() syntax. If we have the following data:

  • Item 1: 15%
  • Item 2: 35%
  • Item 3: 50%

…then we would write down the following conic-gradient():

.gradient {
background:
conic-gradient(
blue 0% 15%,
lightblue 15% 50%,

This basically says: “Paint the first color from 0 to 15%, the next color from 15% to 50% (so the difference is 35%), and so on.”

Do you see the issue? The pie chart is drawn in a single conic-gradient(), which equals a single element. You may not see it, but that’s terrible! If we want to show each item’s weight inside data-percentage — making everything prettier — then we would need a way to access all these percentages from the parent element. That’s impossible!

The only way we can get away with the simplicity of data-percentage is if each item draws its own slice. This doesn’t mean, however, that we can’t use conic-gradient(), but rather we’ll have to use more than one.

[...]


Original source

Reply