# Donut chart

> Parts of a whole as a donut or pie, with the total in the middle, a legend with values and shares, and a selectable slice.

- Type: React component (Charts & Stats)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add donut-chart`
- Docs: https://wingo-ui.com/components/donut-chart

## Interaction

The slices sweep in clockwise from the start angle the first time the chart scrolls into view, while the total in the middle counts up. Hover a slice and it lifts, the others dim and the center reads its value, name and share (a pie, or a donut too small for a center, shows a floating readout instead; the folded Other slice lists what is inside it above the chart). Click a slice to select it: it stays lifted and the center keeps its value (click it again, or click the hole, to clear). On a phone, tap a slice to select it (the ring is widened by 12px on each side, so thin rings and slivers are easy to hit), slide a finger along the ring to scrub (the center reads each slice and the one you let go on is selected), tap the hole or outside the ring to clear. Legend rows are buttons: tap or click one to select its slice, hover or focus it to preview the slice in the center. With the keyboard, Tab to the chart: Left / Right (or Up / Down) walk the slices, Home / End jump to the first and last, Enter or Space selects the inspected slice and Escape clears the selection; a live region reads each slice. When the data changes, the slices glide to their new angles. The legend sits beside the chart from a 480px wide container and below it on phones.

## Installation

```bash
npx wingo-ui@latest add donut-chart
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/donut-chart` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/ui/donut-chart.tsx`

npm dependencies: `npm i motion d3-shape && npm i -D @types/d3-shape`

Wingo UI dependencies (installed automatically):

- [Chart kit](https://wingo-ui.com/components/chart-kit) ([Markdown](https://wingo-ui.com/components/chart-kit.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [useLocale](https://wingo-ui.com/components/use-locale) ([Markdown](https://wingo-ui.com/components/use-locale.md))

## Usage

```tsx
import { DonutChart } from "@/components/ui/donut-chart"

const channels = [
  { id: "direct", label: "Direct sales", value: 48920 },
  { id: "online", label: "Online store", value: 36400 },
  { id: "partners", label: "Partners", value: 22130 },
  { id: "tenders", label: "Public tenders", value: 13600 },
  { id: "other", label: "Other", value: 7400 },
]

export function RevenueByChannel() {
  return (
    <DonutChart
      data={channels}
      valueFormat="currency"
      locale="en-US"
      label="Revenue by channel, September 2026"
      onValueChange={(id) => console.log(id)}
    />
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data` | `DonutChartDatum[] = { id: string; label: ReactNode; value: number; color?: string; name?: string }[]` |  | One entry per slice. color takes a palette slot ("series-3"), a tone ("success", "danger") or any CSS color; name is the plain-text name when label is not a string. Negative values are ignored. |
| `variant?` | `"donut" \| "pie"` | `"donut"` | pie has no hole and no center content (the legend gets a total row instead). |
| `size?` | `"sm" \| "md" \| "lg"` | `"md"` | Diameter 140 / 200 / 260px; it shrinks to the container width. |
| `thickness?` | `number` | `0` | Ring width in px. 0 = auto (18% of the diameter). |
| `gap?` | `number` | `2` | px of surface between slices, measured at the middle of the ring. Slices under 3% give theirs up so they stay visible. |
| `cornerRadius?` | `number` | `4` | Rounded slice corners in px. |
| `startAngle?` | `number` | `0` | Where the first slice starts, in degrees: 0 is 12 o'clock, clockwise. |
| `sort?` | `"value" \| "none"` | `"value"` | value puts the biggest slice first (clockwise from the start angle) and gives it palette slot 1; none keeps the data order. |
| `maxSlices?` | `number` | `6` | Named slices kept; the smaller rest fold into one "Other" slice (labels.other, the neutral gray) whose readout lists them. A single leftover slice is never folded. |
| `colors?` | `string[]` |  | Per-position palette overrides after sorting; a datum's own color wins. |
| `showLegend?` | `boolean` | `true` | The legend: one button per slice with its value and share. |
| `legendPosition?` | `"auto" \| "right" \| "bottom"` | `"auto"` | auto puts the legend beside the chart when the container is at least 480px wide and below it otherwise (a container query, right in the server html). |
| `showPercent?` | `boolean` | `true` | Shares in the legend and the readout. |
| `showValues?` | `boolean` | `true` | Values in the legend. |
| `centerLabel?` | `ReactNode` | `""` | The caption under the center value. Default labels.total ("Total"). |
| `centerValue?` | `ReactNode` |  | Replaces the formatted total ("342"); a trailing unit ("342 invoices") is set smaller. |
| `value?` | `string \| null` |  | The selected slice id when you control it: lifted, the others dimmed, the center shows its value, name and share. null clears. |
| `defaultValue?` | `string \| null` | `"null"` | The slice selected at first, uncontrolled. |
| `onValueChange?` | `(id: string \| null) => void` |  | Called when a slice or legend row is selected or cleared. The folded slice reports DONUT_CHART_OTHER_ID ("__other__"). |
| `valueFormat?` | `ChartValueFormat = "number" \| "compact" \| "currency" \| "percent"` | `"number"` | How values print in the center, legend, readout and table. Percent takes whole numbers (12.4 -> "12.4%" in en-US). |
| `currency?` | `string` | `"USD"` | ISO 4217 code for valueFormat currency, printed with the narrow symbol ("$128,450"). |
| `decimals?` | `number` |  | Fraction digits for values; auto by magnitude when unset. |
| `formatValue?` | `(value: number, context: { series: string; kind: "tooltip" \| "label" \| "table" }) => string` |  | Replaces the built-in value formatting everywhere; series is the slice id ("" for the total). |
| `locale?` | `string` | `""` | Locale for numbers. Empty uses the UIProvider / LocaleProvider locale. |
| `showTooltip?` | `boolean` | `true` | The floating readout for a pie or a donut too small for a center, and the list of folded slices behind Other. A donut with a center reads the inspected slice in the hole either way; selection and announcements work without it. |
| `animated?` | `boolean` | `true` | The clockwise sweep and count-up on first view, and gliding slices on data changes. Reduced motion always shows the final state. |
| `loading?` | `boolean` | `false` | A ring-shaped skeleton and skeleton legend rows in the same box. |
| `emptyState?` | `ReactNode` |  | Replaces the built-in empty state (shown when there is no data or the total is 0). |
| `label?` | `string` | `""` | Accessible name of the figure (followed by "total ...") and caption of its data table. Empty joins the slice names. |
| `description?` | `string` | `""` | A one-sentence takeaway for screen readers ("Direct sales bring in 38% of revenue"). |
| `surface?` | `string` | `""` | The color behind the chart. Empty uses the surface token; the gaps between slices show whatever is behind the chart. |
| `labels?` | `Partial<DonutChartLabels>` |  | Strings: empty, loading, total (the center caption), other, inspectHint, slice / value / percent (table headers), figure ((label, total) => name), more ((count) => "+3 more"). |
| `classNames?` | `Partial<Record<DonutChartSlot, string>>` |  | Classes for plot (the drawing: svg + center), svg, segment, center, centerValue, centerLabel, centerPercent, legend, legendItem, tooltip, table and inspect. |

## Parts

### DEFAULT_DONUT_CHART_LABELS (constant)

The English strings the chart uses (table headers Slice, Value and Share, the figure's accessible name, the "+N more" line of the Other tooltip, the keyboard hint). Spread it and override what you translate.

### DONUT_CHART_OTHER_ID (constant)

The id of the folded "Other" slice ("__other__"). onValueChange reports it when that slice is selected, and you can pass it as value to select it.

### DonutChartDatum (type)

One slice: { id, label, value, color?, name? }. The id is what value and onValueChange carry; color takes a palette slot, a tone or any CSS color.

### DonutChartLabels (type)

The shape of labels: the chart kit's shared strings plus slice, value, percent, figure and more.

## Examples

- **Pie with status tones**: Invoices by status as a pie, colored by tones, with a total row in the legend.
- **Folded into Other**: Ten cities, maxSlices 5: the smallest fold into one gray slice.
- **Sizes and KPI cards**: sm, md and lg, and a small legendless donut inside a KPI card.
- **Controlled selection**: value / onValueChange shared with a list next to the chart.
- **Custom center**: centerValue and centerLabel replace the total.
- **Loading, empty and phone**: Skeletons, the empty state and the stacked phone layout.

## See also

- [Chart kit](https://wingo-ui.com/components/chart-kit) ([Markdown](https://wingo-ui.com/components/chart-kit.md))
- [Radial bar](https://wingo-ui.com/components/radial-bar) ([Markdown](https://wingo-ui.com/components/radial-bar.md))
- [Bar chart](https://wingo-ui.com/components/bar-chart) ([Markdown](https://wingo-ui.com/components/bar-chart.md))
- [Area chart](https://wingo-ui.com/components/area-chart) ([Markdown](https://wingo-ui.com/components/area-chart.md))

---

Canonical: https://wingo-ui.com/components/donut-chart
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
