Components / Charts

DonutChart

A donut whose arcs morph between datasets, with the active value rolling into the center.

Install

npx shadcn@latest add https://irisui.dev/r/donut-chart.json
import { DonutChart } from "@/components/iris/donut-chart/donut-chart";
Registry item donut-chartBrings in foundationRegistry JSON

Preview

DonutChart

A donut for a few parts of one whole, with a synced legend, small parts grouped into Other, and the active value rolling into the centre.

When to use

  • Two to six parts of one total where each part's share matters more than precise comparison: traffic sources, spend by model, plan mix.
  • Highlighting one category from elsewhere on the page: drive activeKey from a table or filter.
  • Not for more than about six parts or for close comparisons; use BarChart. For countable shares, WaffleChart reads more precisely.

Usage

import { DonutChart } from "@/components/iris/donut-chart/donut-chart";

h(DonutChart, {
  label: "Storage by project, CapyDB",
  unit: "GiB",
  totalLabel: "Storage",
  formatValue: (v) => v.toFixed(1),
  data: [
    { key: "myroomie", label: "myroomie", value: 6.8 },
    { key: "entrolytics", label: "entrolytics", value: 5.5 },
    { key: "shop", label: "shop", value: 4.2 },
    { key: "notdocs", label: "notdocs", value: 3.1 },
    { key: "api", label: "api", value: 2.6 },
  ],
});

Props

Prop Type Default Description
data { key: string; label: string; value: number; color?: string }[] required Parts of the whole. Zero or negative values are omitted. Omit color to use --series-1…4, then neutral steps.
label string required Names the whole for assistive technology.
unit string "" Unit under the total at rest.
formatValue (value: number) => string grouped number Centre, legend and table values.
totalLabel string "Total" Centre label above the total.
size / thickness number 208 / 24 Ring diameter and stroke in px; scales down in narrow containers.
groupBelow number 0.04 Parts below this share join Other (when at least two would).
maxSegments number 6 Most segments drawn, counting Other.
otherLabel string "Other" Label of the grouped segment.
activeKey / defaultActiveKey / onActiveChange string | null / callback — / null Pinned segment. Hover and focus preview without changing it.
hiddenKeys / defaultHiddenKeys / onHiddenKeysChange string[] / callback — / [] Hidden segments; the ring redistributes.
legendAction "toggle" | "select" "toggle" Legend click shows/hides or pins a segment.
legend boolean true The synced legend.
emptyLabel string "No data yet" Centre text when the total is zero.
className string — Class on the root figure.
ref Ref<HTMLElement> — Forwarded to the root figure.

Notes

  • Keyboard: the legend is the keyboard path. Arrow keys move between rows (looping), Home/End jump, Enter/Space toggles or pins, Escape clears the pin. Without a legend the ring takes focus and arrows walk the segments.
  • Accessibility: legend rows are buttons with aria-pressed and a label giving value, share and grouped members; show/hide changes are announced with the new total. The ring and centre readout are hidden; a hidden summary and table list every part.
  • Layout gotcha: the legend sits beside the ring only when an ancestor is an inline-size container at least 460px wide (the figure's own @container rule cannot query itself). Give the wrapper container-type: inline-size, as the preview does; otherwise the legend stacks below the ring.
  • Keep keys stable across datasets so arcs morph in place.