I honestly thought I’d seen every way a simple chart could break until I took on a pro-bono project for a kids’ charity last year. They needed a landing page with data, specifically a Semantic CSS Pie Chart, and I admit—I cheated. We were on a tight deadline, so I pulled in a heavy JavaScript library for two tiny charts. It worked, but it felt like using a sledgehammer to crack a nut.
In the WordPress world, we often trade performance for convenience. However, pulling in 50kb of JS for a decorative element is a bad habit. More importantly, those “easy” solutions usually fail our first responsibility: accessibility. If a screen reader can’t explain the data to a user, the chart is essentially a broken image.
Why Conic Gradients Aren’t Enough
If you’ve spent any time in the CSS Almanac, you’ve likely seen the conic-gradient() function. It looks great on paper because you can create a pie shape with a single line of code. Specifically, the syntax is clean and readable for developers.
.gradient {
background: conic-gradient(blue 0% 12.5%, lightblue 12.5% 50%, navy 50% 100%);
}
However, this is where the “Architect’s Critique” begins. Gradients are images. To a screen reader, that element is an empty box. Furthermore, customizing these via HTML is a nightmare because the data is hardcoded into your stylesheet. To build a better Semantic CSS Pie Chart, we need to move the data into the markup.
Structuring Semantic HTML
Our goal is to make the data readable before the CSS even loads. I prefer using a <figure> element with an unordered list. This allows us to use data-attributes to pass values into our CSS. Consequently, the browser can parse the numbers while the screen reader narrates the legend.
<figure>
<figcaption>Charity Donations 2024</figcaption>
<ul class="pie-chart">
<li data-percentage="35" data-color="#ff6666"><strong>Education</strong></li>
<li data-percentage="25" data-color="#4fff66"><strong>Health</strong></li>
<li data-percentage="40" data-color="#66ffff"><strong>Food</strong></li>
</ul>
</figure>
If you’re interested in similar patterns for other chart types, check out my guide on Better CSS Bar Charts which uses similar attr() logic.
Leveraging Trigonometric CSS Functions
Now, we need to place our labels around the circle. In the past, this required absolute positioning hacks. Today, we can use native CSS math functions like cos() and sin(). By defining an angle (theta) and a radius, we calculate the X and Y coordinates directly in our styles.
.pie-chart li {
--radius: 20vmin;
--theta: calc((360deg * var(--weighing)) / 2 + var(--offset) - 90deg);
--pos-x: calc(cos(var(--theta)) * (var(--radius) + 4rem));
--pos-y: calc(sin(var(--theta)) * (var(--radius) + 4rem));
transform: translate(var(--pos-x), var(--pos-y));
}
This approach ensures that our Semantic CSS Pie Chart remains flexible. We use the attr() function—specifically the newer Chromium syntax—to parse data attributes as numbers. While cross-browser support for advanced attr() types is still evolving, it’s the right way to build for the future.
A Pragmatic Touch of JavaScript
I’m a pragmatist. While I’d love a 100% CSS solution, calculating the “accumulated offset” for each slice is mathematically impossible with pure CSS variables due to the cascade. Therefore, I use a tiny bit of Vanilla JS to handle the offsets.
const items = document.querySelectorAll(".pie-chart li");
let accum = 0;
items.forEach((item) => {
item.style.setProperty("--accum", accum);
accum += parseFloat(item.getAttribute("data-percentage"));
});
Look, if this Semantic CSS Pie Chart stuff is eating up your dev hours, let me handle it. I’ve been wrestling with WordPress since the 4.x days.
Shipping Better Code
Moving away from heavy libraries doesn’t just improve your Lighthouse score; it makes your site more resilient. By combining semantic HTML, modern trigonometric functions, and a dash of JS, you get a chart that is accessible, maintainable, and lightweight. Don’t settle for inaccessible gradients—refactor your approach and ship something your users can actually use.