Donut Chart

Pie chart with centre removed.

100% total
Donut chart data
SegmentShare
Segment A48%
Segment B32%
Segment C20%

html

<div class="donut donut--first" style="--t:18;--accent:#A8E6C1;">
  <svg class="donut__ring" width="140" height="140" viewBox="0 0 120 120" aria-hidden="true">
    <g transform="rotate(-90 60 60)">
      <circle class="donut__seg donut__seg--1" cx="60" cy="60" r="40" fill="none" stroke="#A8E6C1" stroke-width="18" stroke-dasharray="120.64 130.69" stroke-dashoffset="0" />
      <circle class="donut__seg donut__seg--2" cx="60" cy="60" r="40" fill="none" stroke="#D9C8F0" stroke-width="18" stroke-dasharray="80.43 170.9" stroke-dashoffset="-120.64" />
      <circle class="donut__seg donut__seg--3" cx="60" cy="60" r="40" fill="none" stroke="#BFD9F2" stroke-width="18" stroke-dasharray="50.27 201.06" stroke-dashoffset="-201.07" />
    </g>
  </svg>
  
  <span class="donut__center">
    <strong class="donut__total">100%</strong>
    <span class="donut__total-label">total</span>
  </span>
  
  <ul class="donut__legend">
    <li class="donut__item donut__item--1"><span class="donut__swatch donut__swatch--1" aria-hidden="true"></span>Segment A &mdash; 48%<span class="donut__flag">highlighted</span></li>
    <li class="donut__item donut__item--2"><span class="donut__swatch donut__swatch--2" aria-hidden="true"></span>Segment B &mdash; 32%<span class="donut__flag">highlighted</span></li>
    <li class="donut__item donut__item--3"><span class="donut__swatch donut__swatch--3" aria-hidden="true"></span>Segment C &mdash; 20%<span class="donut__flag">highlighted</span></li>
  </ul>
  <table class="donut__srtable">
    <caption>Donut chart data</caption>
    <thead><tr><th scope="col">Segment</th><th scope="col">Share</th></tr></thead>
    <tbody>
      <tr><th scope="row">Segment A</th><td>48%</td></tr>
      <tr><th scope="row">Segment B</th><td>32%</td></tr>
      <tr><th scope="row">Segment C</th><td>20%</td></tr>
    </tbody>
  </table>
</div>
<style>
.donut { font: 14px/1.4 system-ui, sans-serif; color: #241430; max-width: 320px; position: relative; }
.donut__ring { display: block; margin: 0 auto; }
@media (prefers-reduced-motion: no-preference) {
.donut__seg { transition: stroke-width .15s ease; }
}
.donut__center { position: absolute; top: 70px; left: 70px; transform: translate(-50%, -50%); display: flex; flex-direction: column; align-items: center; text-align: center; }
.donut__total { font-size: 20px; }
.donut__total-label { font-size: 11px; color: #241430aa; }
.donut__legend { list-style: none; margin: 12px 0 0; padding: 0; font-size: 13px; }
.donut__item { display: flex; align-items: center; gap: 8px; padding: 3px 0; }
.donut__swatch { width: 12px; height: 12px; border-radius: 3px; flex: none; }
.donut__swatch--1 { background: #A8E6C1; }
.donut__swatch--2 { background: #D9C8F0; background-image: repeating-linear-gradient(45deg, #241430 0 2px, transparent 2px 5px); }
.donut__swatch--3 { background: #BFD9F2; background-image: radial-gradient(#241430 1px, transparent 1px); background-size: 5px 5px; }
.donut__flag { display: none; margin-left: 4px; font-weight: 700; font-size: 11px; }
.donut__srtable { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.donut--first .donut__seg--1 { stroke-width: calc(var(--t) + 6); }
.donut--first .donut__item--1 .donut__flag { display: inline; }
.donut--second .donut__seg--2 { stroke-width: calc(var(--t) + 6); }
.donut--second .donut__item--2 .donut__flag { display: inline; }
.donut--third .donut__seg--3 { stroke-width: calc(var(--t) + 6); }
.donut--third .donut__item--3 .donut__flag { display: inline; }
</style>

tailwind

<div class="donut-tw donut-tw--first relative max-w-xs font-sans text-sm" style="--t:18;--accent:#A8E6C1;">
  <svg class="block mx-auto" width="140" height="140" viewBox="0 0 120 120" aria-hidden="true">
    <g transform="rotate(-90 60 60)">
      <circle class="donut-tw__seg donut-tw__seg--1" cx="60" cy="60" r="40" fill="none" stroke="#A8E6C1" stroke-width="18" stroke-dasharray="120.64 130.69" stroke-dashoffset="0" />
      <circle class="donut-tw__seg donut-tw__seg--2" cx="60" cy="60" r="40" fill="none" stroke="#D9C8F0" stroke-width="18" stroke-dasharray="80.43 170.9" stroke-dashoffset="-120.64" />
      <circle class="donut-tw__seg donut-tw__seg--3" cx="60" cy="60" r="40" fill="none" stroke="#BFD9F2" stroke-width="18" stroke-dasharray="50.27 201.06" stroke-dashoffset="-201.07" />
    </g>
  </svg>
  
  <span class="absolute flex flex-col items-center text-center" style="top:70px;left:70px;transform:translate(-50%,-50%);">
    <strong class="text-xl">100%</strong>
    <span class="text-[11px] text-[#241430aa]">total</span>
  </span>
  
  <ul class="list-none m-0 mt-3 p-0 text-[13px]">
    <li class="donut-tw__item donut-tw__item--1 flex items-center gap-2 py-[3px]"><span class="w-3 h-3 rounded" style="background:#A8E6C1" aria-hidden="true"></span>Segment A &mdash; 48%<span class="donut-tw__flag hidden ml-1 font-bold text-[11px]">highlighted</span></li>
    <li class="donut-tw__item donut-tw__item--2 flex items-center gap-2 py-[3px]"><span class="w-3 h-3 rounded" style="background:#D9C8F0;background-image:repeating-linear-gradient(45deg,#241430 0 2px,transparent 2px 5px)" aria-hidden="true"></span>Segment B &mdash; 32%<span class="donut-tw__flag hidden ml-1 font-bold text-[11px]">highlighted</span></li>
    <li class="donut-tw__item donut-tw__item--3 flex items-center gap-2 py-[3px]"><span class="w-3 h-3 rounded" style="background:#BFD9F2;background-image:radial-gradient(#241430 1px,transparent 1px);background-size:5px 5px" aria-hidden="true"></span>Segment C &mdash; 20%<span class="donut-tw__flag hidden ml-1 font-bold text-[11px]">highlighted</span></li>
  </ul>
  <table class="absolute w-px h-px overflow-hidden" style="clip:rect(0 0 0 0)">
    <caption>Donut chart data</caption>
    <thead><tr><th scope="col">Segment</th><th scope="col">Share</th></tr></thead>
    <tbody>
      <tr><th scope="row">Segment A</th><td>48%</td></tr>
      <tr><th scope="row">Segment B</th><td>32%</td></tr>
      <tr><th scope="row">Segment C</th><td>20%</td></tr>
    </tbody>
  </table>
</div>
<style>
@media (prefers-reduced-motion: no-preference) {
.donut-tw__seg { transition: stroke-width .15s ease; }
}
.donut-tw--first .donut-tw__seg--1 { stroke-width: calc(var(--t) + 6); }
.donut-tw--first .donut-tw__item--1 .donut-tw__flag { display: inline; }
.donut-tw--second .donut-tw__seg--2 { stroke-width: calc(var(--t) + 6); }
.donut-tw--second .donut-tw__item--2 .donut-tw__flag { display: inline; }
.donut-tw--third .donut-tw__seg--3 { stroke-width: calc(var(--t) + 6); }
.donut-tw--third .donut-tw__item--3 .donut-tw__flag { display: inline; }
</style>

react

export function DonutChart({
  highlightSegment = "first",
  thickness = 18,
  accent = "#A8E6C1",
  showCenterLabel = true,
}) {
  const segs = [
    { key: "1", name: "Segment A", pct: 48, color: accent, len: 120.64, gap: 130.69, offset: 0 },
    { key: "2", name: "Segment B", pct: 32, color: "#D9C8F0", len: 80.43, gap: 170.9, offset: -120.64 },
    { key: "3", name: "Segment C", pct: 20, color: "#BFD9F2", len: 50.27, gap: 201.06, offset: -201.07 },
  ];
  const highlightIndex = { first: "1", second: "2", third: "3" }[highlightSegment] ?? "1";
  return (
    <div style={{ fontFamily: "system-ui, sans-serif", color: "#241430", maxWidth: 320, position: "relative", fontSize: 14 }}>
      <svg width={140} height={140} viewBox="0 0 120 120" aria-hidden="true" style={{ display: "block", margin: "0 auto" }}>
        <g transform="rotate(-90 60 60)">
          {segs.map((s) => (
            <circle key={s.key} cx={60} cy={60} r={40} fill="none" stroke={s.color}
              strokeWidth={s.key === highlightIndex ? thickness + 6 : thickness}
              strokeDasharray={`${s.len} ${s.gap}`} strokeDashoffset={s.offset} />
          ))}
        </g>
      </svg>
      {showCenterLabel && (
        <span style={{ position: "absolute", top: 70, left: 70, transform: "translate(-50%, -50%)", display: "flex", flexDirection: "column", alignItems: "center", textAlign: "center" }}>
          <strong style={{ fontSize: 20 }}>100%</strong>
          <span style={{ fontSize: 11, color: "#241430aa" }}>total</span>
        </span>
      )}
      <ul style={{ listStyle: "none", margin: "12px 0 0", padding: 0, fontSize: 13 }}>
        {segs.map((s) => (
          <li key={s.key} style={{ display: "flex", alignItems: "center", gap: 8, padding: "3px 0" }}>
            <span aria-hidden="true" style={{ width: 12, height: 12, borderRadius: 3, background: s.color }} />
            {s.name} &mdash; {s.pct}%
            {s.key === highlightIndex && <span style={{ marginLeft: 4, fontWeight: 700, fontSize: 11 }}>highlighted</span>}
          </li>
        ))}
      </ul>
      <table style={{ position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)" }}>
        <caption>Donut chart data</caption>
        <thead><tr><th scope="col">Segment</th><th scope="col">Share</th></tr></thead>
        <tbody>
          {segs.map((s) => (
            <tr key={s.key}><th scope="row">{s.name}</th><td>{s.pct}%</td></tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}