Donut chart

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

datanew
Preview
Donut chart preview

Visits by source. Visits 93,561 visits. Search, 42,134 visits, 45%. Direct, 21,809 visits, 23%. Social, 15,006 visits, 16%. Referral, 9,210 visits, 10%. Newsletter, 5,402 visits, 6%.

Visits by source
SegmentValueShare
Search42,134 visits45%
Direct21,809 visits23%
Social15,006 visits16%
Referral9,210 visits10%
Newsletter5,402 visits6%

When to use

  • Traffic by source, spend by category, or storage by file type.
  • A dashboard card where the total and one highlighted share matter most.
  • Switching between datasets, such as this month and last month, with the same categories.

When not to use

  • Use bar-chart when precise comparison between parts matters or there are many parts.
  • Use gauge or usage-meter for a single value against a limit.
  • Use line-chart for change over time.

shadcn CLI

Also installs (1)

  • manicat-foundation
Manual install(+)
registry.jsonjson
{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "donut-chart",
  "type": "registry:ui",
  "title": "Donut chart",
  "description": "A donut whose arcs morph between datasets, with the active value rolling into the center.",
  "categories": [
    "data"
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://manicat.viraza.net/r/manicat-foundation.json"
  ],
  "files": [
    {
      "path": "registry/components/donut-chart/donut-chart.tsx",
      "type": "registry:component",
      "target": "@components/manicat/donut-chart/donut-chart.tsx"
    },
    {
      "path": "registry/components/donut-chart/donut-chart.module.css",
      "type": "registry:component",
      "target": "@components/manicat/donut-chart/donut-chart.module.css"
    }
  ],
  "docs": "Docs and live preview: https://uiarc.dev/components/donut-chart",
  "meta": {
    "tier": "free",
    "kind": "component",
    "docs": "https://manicat.viraza.net/components/donut-chart",
    "markdown": "https://uiarc.dev/components/donut-chart/markdown",
    "tags": [
      "data",
      "new"
    ]
  }
}

Usage

Install the component, then copy this into your app. Change the content and the props to fit your screen.

example.tsxtsx
"use client";

import { DonutChart } from "@/components/manicat/donut-chart/donut-chart";
import type { DonutChartDatum } from "@/components/manicat/donut-chart/donut-chart";

const donutData: DonutChartDatum[] = [
  { key: "search", label: "Search", value: 42_134 },
  { key: "direct", label: "Direct", value: 21_809 },
  { key: "social", label: "Social", value: 15_006 },
  { key: "referral", label: "Referral", value: 9_210 },
  { key: "newsletter", label: "Newsletter", value: 5_402 },
];

export function DonutChartExample() {
  // Size containers have no width of their own: give the demo the whole row.
  return <div className="w-full"><DonutChart data={donutData} label="Visits by source" unit="visits" totalLabel="Visits" legend /></div>;
}

Examples

Real screens rather than isolated props. Switch to Code to copy a whole example into your project.

Traffic sources Parts of a whole, with the total in the middle and a legend that highlights slices.

Traffic sources Parts of a whole, with the total in the middle and a legend that highlights slices. preview

Visits by source. Visits 88,159 visits. Search, 42,134 visits, 48%. Direct, 21,809 visits, 25%. Social, 15,006 visits, 17%. Referral, 9,210 visits, 10%.

Visits by source
SegmentValueShare
Search42,134 visits48%
Direct21,809 visits25%
Social15,006 visits17%
Referral9,210 visits10%

Keyboard interactions

KeysAction
ArrowDown / ArrowRightMoves to the next legend row, looping at the end.
ArrowUp / ArrowLeftMoves to the previous legend row.
Home / EndJumps to the first or last row.
Enter / SpaceShows or hides the focused segment; with legendAction select, pins or unpins it.
EscapeClears the pinned segment.
Arrow keys on the ringWithout a legend the ring takes focus: arrows walk the segments, Enter pins one, and each is announced.

Accessibility

  • The legend is the keyboard path: each row is a button with aria-pressed (shown, or pinned with legendAction select) and a label giving value, share, and grouped members.
  • Showing or hiding a segment is announced in a polite live region with the new total. Without a legend the ring is focusable and announces each segment as the arrows reach it.
  • The ring and center readout are aria-hidden; a hidden summary and table list every part, including the members of Other.
  • Keyboard focus previews a segment in the ring; hover previews with a mouse only. Pointer focus draws no ring; keyboard focus shows Arc's shared focus ring.

Motion

  • One sweep the first time the chart scrolls into view: every segment leaves the top together and each trailing edge follows a beat behind the one before.
  • New data and hidden segments morph start and end angles, never paths, from wherever each arc is on screen. Springs keep their velocity, so an interrupted change continues smoothly. Segments keep their order and never remount mid-morph.
  • The active segment slides out along its middle and thickens slightly while the others dim. One pointer handler hit-tests the angle, so crossing a gap never drops the hover.
  • The center readout rolls like a drum toward the active segment with tabular numbers in a fixed cell; totals and shares count to new values.
  • Reduced motion jumps arcs, lifts, and text to their final state.

Responsive behavior

  • The figure is a container: at 460px and wider the legend sits beside the ring, below that it stacks under it.
  • The ring scales down from size to fit narrower containers while keeping its aspect ratio.
  • Legend hover styles only apply on hover-capable fine pointers; taps pin a segment.

Performance

  • Arc angles and lifts are motion values; one batched paint per frame writes every path d and transform straight to the DOM, with no React render per frame.
  • Counting numbers write their text directly; center readouts stay mounted, so hovering never mounts or unmounts nodes.

Notes for AI

  • Use for two to six parts of one whole. With more parts, lean on groupBelow and maxSegments or use bar-chart.
  • Keep keys stable between datasets so arcs morph in place.
  • Leave color out to get the shared --series-1 to --series-4 palette in data order, then neutral steps; a key keeps its color across datasets. Pass colors only when they carry meaning.
  • Legend rows show and hide segments by default; set legendAction to select for the older pin behavior.
  • Drive activeKey from a table or filter to highlight the same category elsewhere.