Skip to content
WANCRAFT

G.87Data & Feedback

Donut Chart

An interactive donut: arcs sweep in, segments slide out on hover or focus, the legend re-flows the ring.

NewReactSVGTailwindadvanced

Overview

Donut Chart draws each segment as a stroked SVG arc with optional rounded caps and true pad angles (caps are pulled in so the gaps stay exact). On mount the arcs sweep in from twelve o'clock; when data changes or a legend button hides a segment, every arc tweens from where it is on screen to its new span, so the ring re-flows instead of jumping. Hovering, tapping or focusing a segment slides it outward and puts its label, value and share in the centre. The ring is one tab stop with arrow-key navigation between segments, the legend buttons are real toggles, and a visually hidden table carries the full data. Colours follow each datum's id, so hiding one never repaints the rest.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/donut-chart.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/donut-chart.json

Method 02 · Copy and paste

  1. 1. No extra dependencies — React and Tailwind CSS only.
  2. 2. Copy the source from the Code tab into components/ui/donut-chart.tsx.
  3. 3. Import it and adjust props.
Dependencies
None
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { DonutChart, type DonutDatum } from "@/components/ui/donut-chart";

export function DonutChartExample() {
  const data: DonutDatum[] = [
    { id: "design", label: "Design", value: 420 },
    { id: "build", label: "Build", value: 610 },
    { id: "motion", label: "Motion", value: 260 },
    { id: "research", label: "Research", value: 140 },
  ];

  return (
    <DonutChart
      label="Studio hours by discipline"
      data={data}
      format={(v) => `${v} h`}
    />
  );
}

03Props

PropTypeDefaultDescription
data{ id: string; label: string; value: number; color?: string }[]—Segments (required). Pass a new array to re-flow; ids keep colours and arcs stable.
labelstring—Accessible name and caption of the screen-reader table (required).
format(value: number) => string—Value formatter for the centre, legend, labels and table.
thicknessnumber28Ring thickness in px at the 240px reference size (scales with the chart).
padAnglenumber1.5Gap between segments, in degrees.
roundedbooleantrueRounded segment ends; caps are pulled in so gaps stay exact.
durationnumber900Entry and re-flow tween, in ms.
expandnumber6How far the active segment slides out, in px.
legend"right" | "bottom" | "none""right"Legend placement (right falls back to below in narrow containers).
centerLabelstring"Total"Caption above the total when nothing is active.
sizenumber260Maximum chart diameter in px.
accentstring"#FF7448"Colour of the first segment without its own colour.

04Features

  • 01Stroked SVG arcs with rounded caps and exact pad angles
  • 02Entry sweep and smooth re-flow from the arcs on screen (rAF tween, no React render per frame)
  • 03Hover, tap or focus slides a segment out and shows it in the centre
  • 04Legend toggles hide and show segments
  • 05Colour follows the datum (accent + five CVD-checked slots, neutral beyond), stepped for dark mode
  • 06Container-query layout: legend beside or below the ring
motion · tweeninput · hoverinput · clickinput · keyboardinput · focus

05Accessibility

  • The ring is a single tab stop (roving tabindex); arrow keys, Home and End move between visible segments, Escape clears the highlight.
  • Each focusable segment is a list item labelled with its name, value and share; a hint about the arrow keys is attached via aria-describedby.
  • Legend items are buttons with aria-pressed, so hiding a segment is an announced toggle.
  • A visually hidden table lists every segment with value and share of the full total.
  • Keyboard focus draws a high-contrast ring around the segment; identity is never colour alone (labels in the legend and centre).
  • With prefers-reduced-motion arcs are drawn without tweening and segments do not slide.

06Limitations

  • Segments are drawn on the client after hydration; the server HTML contains the table and legend but no arcs.
  • Very small shares (under the cap size) render as a dot that may touch its neighbours with thick rounded rings.