Data visualization is a cornerstone of modern web development. Pie charts, in particular, offer an intuitive way to represent proportions. However, developers often reach for heavy JavaScript charting libraries for convenience, impacting page performance and potentially neglecting accessibility. This tutorial walks you through building a pie chart that prioritizes semantic HTML and accessibility, leveraging CSS features like conic-gradient() and modern CSS functions. You can find the foundational concepts in the source material.

Crafting the Semantic HTML Foundation
First, we need a meaningful HTML structure that screen readers can interpret. We'll use figure and ul to structure our data.
<figure class="pie-chart">
<figcaption>Candies Sold Last Month</figcaption>
<ul>
<li data-percentage="30" data-color="#4A90E2">
<strong>Chocolates</strong>
</li>
<li data-percentage="25" data-color="#50E3C2">
<strong>Gummies</strong>
</li>
<li data-percentage="20" data-color="#F5A623">
<strong>Hard Candy</strong>
</li>
<li data-percentage="25" data-color="#BD10E0">
<strong>Bubble Gum</strong>
</li>
</ul>
</figure>
Drawing the Slices with CSS
Each li element will read its own data-percentage and data-color attributes to draw a unique slice. We use the modern attr() function with type parsing to convert HTML attributes into CSS variables.
.pie-chart {
display: grid;
place-items: center;
}
.pie-chart li {
--radius: 20vmin;
--percentage: calc(attr(data-percentage number) * 1%);
--bg-color: attr(data-color color);
--weighing: calc(attr(data-percentage number) / 100);
width: calc(var(--radius) * 2);
aspect-ratio: 1;
border-radius: 50%;
grid-row: 1;
grid-column: 1;
/* Create slice using conic-gradient */
background: conic-gradient(
from var(--offset, 0deg),
var(--bg-color) 0% var(--percentage),
transparent var(--percentage) 100%
);
}
Calculating Cumulative Values with JavaScript
CSS alone cannot accumulate the percentages of previous slices. A small JavaScript snippet calculates the --accum variable to set the starting angle for each slice.
const pieChartItems = document.querySelectorAll('.pie-chart li');
let accum = 0;
pieChartItems.forEach((item) => {
item.style.setProperty('--accum', accum);
accum += parseFloat(item.getAttribute('data-percentage'));
item.style.setProperty('--offset', `${accum * 3.6}deg`);
});

Positioning Labels in a Circle
We use the cos() and sin() CSS functions to position labels at the midpoint of each slice around the circle, bringing mathematical coordinate calculation directly into CSS.
.pie-chart li {
/* ... existing styles ... */
/* Calculate label position */
--theta: calc((360deg * var(--weighing)) / 2 + var(--offset) - 90deg);
--gap: 4rem;
--pos-x: calc(cos(var(--theta)) * (var(--radius) + var(--gap)));
--pos-y: calc(sin(var(--theta)) * (var(--radius) + var(--gap)));
display: grid;
place-items: center;
}
.pie-chart li strong,
.pie-chart li::after {
grid-row: 1;
grid-column: 1;
transform: translateX(var(--pos-x)) translateY(var(--pos-y));
}
/* Display percentage */
.pie-chart li::after {
content: attr(data-percentage) '%';
--pos-y: calc(sin(var(--theta)) * (var(--radius) + var(--gap)) + 1lh);
}
Limitations and Browser Support
While this solution is close to pure CSS, it still requires minimal JavaScript to calculate the cumulative angle for slices. Also, support for cos() and sin() is not yet universal. As a fallback, you could combine CSS transform: rotate(), though calculations become more complex. Colors are hard-coded via the data-color attribute; dynamic color generation would require the color-mix() function or additional JavaScript.

Next Steps and Enhancements
This implementation serves as a solid foundation. Here are directions for further learning and improvement:
- Calculate Percentages from Raw Data: Enhance the markup to accept raw values via a
data-valueattribute and automatically calculate percentages relative to the total sum using CSS or JS. - Adapt for Other Chart Types: Apply the same semantic markup principles to create bar charts or donut charts.
- Add Interactivity: Implement
:hovereffects to highlight slices or display tooltips, significantly improving user experience. - Explore CSS Houdini: In the future, as the
CSS Properties and Values APIgains broader support, it may allow state accumulation within CSS itself, eliminating the need for JavaScript.
Accessible and performant data visualization is a key skill in modern web development. This tutorial aims to empower you to solve problems with foundational web technologies, reducing reliance on heavy libraries.