Data✨ Premium

Donut Chart

Donut chart with the total in the middle and a legend with figures: segments grow clockwise one after the other when the ring appears.

SVGCircular

Updated

This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Data has 28 effects, including 1 free. Explore the category →

3 usage examples

A flatshare's shared spending for the month in a budgeting app — Donut Chart example 1

① A flatshare's shared spending for the month in a budgeting app

WhenThe “November” screen of an app for splitting bills between flatmates: while accounts sync, a grey skeleton fills the card; once they are loaded, the ring appears and fills, total in the middle, legend on the right, while the next column lists the latest expenses.
WhyThe month's total is the first thing people want to know and the split the second: the ring gives both at a glance, and its filling marks the end of loading.
Settingsdata-currency="EUR" (total “€1,240” on the English page), centre label “Total”, --dnc-size: 138px, --dnc-total-size: 1.3rem, original colours, dark text on a white card; the chart goes from hidden to visible when syncing ends.
Contents of the weekly box unfolded on a farm shop's product card — Donut Chart example 2

② Contents of the weekly box unfolded on a farm shop's product card

WhenA farm's direct-sale web shop: the “Seasonal basket” card (photo, price, “Contents” button) with two small products next to it. The button unfolds the card and a 100 px ring fills with the total weight in the middle and the legend in kilos.
WhyBefore buying a surprise box, people want to know what is really in it. The detail stays folded to keep the card compact and shows on request.
Settingsdata-unit="kg" (computed total “4.2 kg”), --dnc-size: 100px, --dnc-thickness: 20, --dnc-total-size: 1.05rem, green, orange, yellow, brown; a button with aria-expanded that removes hidden.
Weekly summary in hours, opened from a rest-diary app notification — Donut Chart example 3

③ Weekly summary in hours, opened from a rest-diary app notification

WhenOn Sunday evening, the home screen of a rest-diary app shows the “Your week is ready” notification. Tapping it opens a sheet with the ring of logged hours (sleep, meditation, reading, screens), 72 h in the middle, and a comparison line.
WhyThe week summed up in one simple shape, with sleep taking two thirds of the ring: the message gets across before reading any number.
Settingsdata-unit="h", centre label “logged”, --dnc-size: 156px, indigo, teal, amber, pink; the sheet is a hidden removed on tap.

How it works

The data are the legend items: each li in ul.dnc-legend has a label and a data-value. The script adds up the values, draws an SVG in .dnc-ring with one circle per segment (radius 40, width --dnc-thickness, 24 by default) and gives each a proportional length: the ring always goes exactly all the way round, with a thin 1.5-unit gap between two segments so they never overlap.

In the middle, .dnc-total receives the sum formatted by Intl.NumberFormat in the page language: data-currency="USD" gives “$1,240” in English, data-currency="EUR" “1 240 €” in French; data-unit appends a unit (h, kg), data-decimals sets the number of decimals and data-compact shortens it (“12K”). Text written in data-total replaces the computed sum. If the total is too wide for the hole, its font size is reduced to fit, and recomputed when the ring changes size. Each legend row gets a swatch and its value in the same format.

Each segment starts in place with zero length. When 35% of the ring is visible, the dnc-on class gives the strokes their final length, with a 1 s transition (ease-out) offset by 0.15 s per segment: the ring fills piece by piece, once.

Accessibility

  • What is read: the total and its label (“$1,240 Total”), then the legend, a list named by its aria-label whose rows read “Housing $480”. The SVG is hidden from screen readers.
  • Reduced motion: under prefers-reduced-motion: reduce, the transition is removed and the ring shows complete.
  • Hideable legend: data-legend="hidden" removes the legend from the screen but not from screen readers.
  • Any background: the hole is truly empty (no painted disc), it shows your page background, light or dark; just adapt --dnc-text and --dnc-muted.

Browser compatibility

SVG created by the script, CSS transition on stroke-dasharray, aspect-ratio, IntersectionObserver, ResizeObserver and Intl.NumberFormat (currencies, compact notation). Measured on 8 Nov 2026 in Chromium 151, Firefox 153 and WebKit 26.5 (Playwright): ring filled to 100% (480/320/140/300), two independent copies, reduced motion, 390 px, no errors.

Chrome 88+✓ Full (measured in Chromium 151)
Firefox 89+✓ Full (measured in Firefox 153)
Safari 15+✓ Measured in WebKit 26.5, not in real Safari
Edge 88+✓ Full (Chromium engine)
Safari iOS 15+✓ Not checked on a device
Chrome Android 88+✓ Full (Chromium engine)

Without IntersectionObserver, the ring fills on load. Without JavaScript, what remains is the legend (labels only) and the word “Total”.

The code

HTML structure to paste into your page (the CSS + JS come with your purchase):

index.html — structure
<div class="fx-0231-root" data-currency="USD">
  <div class="dnc-ring">
    <div class="dnc-center">
      <div class="dnc-total"></div>
      <div class="dnc-label">Total</div>
    </div>
  </div>
  <ul class="dnc-legend" aria-label="Spending this month">
    <li data-value="480">Housing</li>
    <li data-value="320">Groceries</li>
    <li data-value="140">Energy</li>
    <li data-value="300">Going out</li>
  </ul>
</div>

One-time payment, no subscription — the code is yours forever

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Segments (HTML — li data-value="…" in .dnc-legend) Housing 480, Groceries 320, Energy 140, Going out 300 One row per segment; the label is the li text, the value can be in any unit.
Number format (HTML — data-currency or data-unit on the root) data-currency="USD" (EN page), "EUR" (FR page) ISO currency code for a localised amount, or a free unit appended after the number.
Decimals and shortening (HTML — data-decimals, data-compact) 0 for a currency, 1 otherwise; no shortening data-decimals="2" for cents; data-compact writes “12K” instead of “12,000”.
Centre text (HTML — data-total on the root, text of .dnc-label) Computed sum, “Total” data-total forces the displayed text (e.g. an average); change the label in the HTML.
Legend (HTML — data-legend="hidden") Visible Hidden on screen, still read out.
Ring (CSS — --dnc-size, --dnc-thickness, --dnc-total-size) 180 px, 24 (out of 120), 1.5rem Maximum diameter, thickness in drawing units, starting size of the total (shrunk automatically if needed).
Colours (CSS — --dnc-c1 to --dnc-c6) #6366f1, #8b5cf6, #d946ef, #ec4899, #f43f5e, #06b6d4 One colour per segment, in legend order, repeating after six.
Background and text (CSS — --dnc-bg, --dnc-text, --dnc-muted, --dnc-min-height) #0a0a0f, white, 50% white, 300 px Root background, main text, secondary text and minimum height.

FAQ

A 1.5-unit gap separates two segments: without it, two close colours blend together and, with rounded ends, they would spill over each other. The ring stays accurate: each segment keeps its exact share of the circle.
No. The script measures the text and shrinks it until it fits in the hole, then does it again if the ring changes size. For a shorter look, use data-compact (“$128K”).
Yes: set --dnc-bg: transparent and darken --dnc-text and --dnc-muted. The centre is not painted, so no black disc is left in the middle.