Home/ Cookbook/ Charts/ Donut

Pair a composition with a focal stat — donut chart

By the end of this recipe you'll have a donut chart with a centre-label slot — the ring shows the mix, the centre shows the headline number.

Charts Intermediate ~10 min React · @corelithzw/react

Overview

A pie chart with the centre punched out. The hole is real estate — that's where the headline number lives.

Donuts work where pies don't quite, because the centre is a built-in slot for the focal stat. The reader gets two things at once: a composition read (the slices) and an aggregate read (the number in the hole). Use them when the headline number and the mix are equally important — collection rate by source, conversion rate by channel, payments by method.

Skip donuts for more than 4–5 slices (humans cannot read fine angles), or when you need precise comparison between similar-sized slices (bars beat any radial chart for that). Two slices = "use a progress ring instead, you're not really comparing two things".

Default opinion: the centre is the point. If you don't have a focal number to put there, you don't have a donut — you have a pie with a hole.

The chart

Payments collected by method this month. The centre carries the collection rate — the slices say where the money came from.

Payments by method, May Donut: EcoCash 48%, card 28%, cash 18%, bank transfer 6%. Centre shows 92% collection rate. 92% collection rate $48,210 / $52,400 EcoCash48% · $23,100 Card28% · $13,500 Cash18% · $8,680 Bank transfer6% · $2,930
$48,210 collected of $52,400 due — 92%. EcoCash drives nearly half; card and cash together another 46%; bank transfer the small tail.

Required pieces

From @corelithzw/react.

Intended exports used here: Chart.Donut, Chart.Legend.

Roadmap. Chart.Donut ships in @corelithzw/react v0.2.

The React snippet

Pass slices and a centre slot. The centre is just a React node so you can put anything in it.

92% collected
PaymentsByMethod.tsx@corelithzw/react

Customising

Three forks.

Thicker ring

For dashboards where the donut is the hero, push the stroke width up so the slices read at a glance.

<Chart.Donut
  data={slices}
  thickness={48}
  height={320}
/>

Highlight one slice

Pop one slice outward as an emphasis when the data deserves it. Reserve for one slice at a time.

<Chart.Donut
  data={slices}
  emphasis="EcoCash"   {/* slice label */}
  height={300}
/>

Gauge mode

Render as a half-donut (0–180°) when the value is a single ratio against a target.

<Chart.Donut
  data={[{ value: 0.92 }]}
  variant="gauge"
  centre={<>92%</>}
  height={220}
/>

In context

Inside a stat-hero card. The big number anchors the page; the donut is the supporting detail.

Collection rate · May
$48,210 of $52,400
92% collected
EcoCash continues to dominate at 48% of receipts, followed by card at 28%. Cash is in slow decline; bank transfer is the small tail.

Accessibility

Radial charts are the hardest for assistive tech — the legend earns its keep here.

  • Role and label. Root <svg> uses role="img" + aria-labelledby on <title> + <desc>.
  • Centre is a label, not decoration. The big number inside the donut has aria-hidden="true" and the same value is included in the <desc> — screen readers hear it once.
  • Per-slice labels. Each slice gets a <g aria-label="EcoCash: 48 percent, $23,100"> so the percentages and currency are announced.
  • SR summary. A .ck-sr-only paragraph reads the ranking and the collection rate plain.
  • Keyboard. Each slice is tabindex="0"; Arrow keys walk clockwise around the ring.