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.

A colorful pie chart displayed on a web developer's screen with code editor in the background Programming Illustration

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`);
});

Close-up view of a semantic HTML structure for a pie chart with data attributes highlighted Coding Session Visual

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.

Diagram showing the overlay of multiple conic-gradient slices to form a complete pie chart Developer Related Image

Next Steps and Enhancements

This implementation serves as a solid foundation. Here are directions for further learning and improvement:

  1. Calculate Percentages from Raw Data: Enhance the markup to accept raw values via a data-value attribute and automatically calculate percentages relative to the total sum using CSS or JS.
  2. Adapt for Other Chart Types: Apply the same semantic markup principles to create bar charts or donut charts.
  3. Add Interactivity: Implement :hover effects to highlight slices or display tooltips, significantly improving user experience.
  4. Explore CSS Houdini: In the future, as the CSS Properties and Values API gains 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.