Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Charts & Stats

Donut chart

Charts & Stats

Donut chart React component for Tailwind CSS v4

Bar chartFunnel chart
Live props
Appearance
variant
thicknessAuto
gap2px
cornerRadius4px
startAngle0°
surface
Layout
size
showLegend
legendPosition
Content
sort
maxSlices6
showPercent
showValues
centerLabel
valueFormat
currency
locale
label
description
Behavior
showTooltip
animated
State
loading
Code
import { DonutChart } from "@/components/ui/donut-chart"

<DonutChart />
  1. Home
  2. Components
  3. Charts & Stats
  4. Donut chart
Pro

Donut chart React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

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.

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.

Donut chart is a Pro React component in the React chart and stat components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add donut-chart, together with Chart kit, Motion presets, UIProvider, useControllableState and useLocale and the npm packages motion and d3-shape.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add donut-chart

Donut chart is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import DonutChart from @/components/ui/donut-chart.

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

  • 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.

  • variantdefault "donut"
    "donut" | "pie"

    pie has no hole and no center content (the legend gets a total row instead).

  • sizedefault "md"
    "sm" | "md" | "lg"

    Diameter 140 / 200 / 260px; it shrinks to the container width.

  • thicknessdefault 0
    number

    Ring width in px. 0 = auto (18% of the diameter).

  • gapdefault 2
    number

    px of surface between slices, measured at the middle of the ring. Slices under 3% give theirs up so they stay visible.

  • cornerRadiusdefault 4
    number

    Rounded slice corners in px.

  • startAngledefault 0
    number

    Where the first slice starts, in degrees: 0 is 12 o'clock, clockwise.

  • sortdefault "value"
    "value" | "none"

    value puts the biggest slice first (clockwise from the start angle) and gives it palette slot 1; none keeps the data order.

  • maxSlicesdefault 6
    number

    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.

  • showLegenddefault true
    boolean

    The legend: one button per slice with its value and share.

  • legendPositiondefault "auto"
    "auto" | "right" | "bottom"

    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).

  • showPercentdefault true
    boolean

    Shares in the legend and the readout.

  • showValuesdefault true
    boolean

    Values in the legend.

  • centerLabeldefault ""
    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.

  • defaultValuedefault "null"
    string | 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__").

  • valueFormatdefault "number"
    ChartValueFormat = "number" | "compact" | "currency" | "percent"

    How values print in the center, legend, readout and table. Percent takes whole numbers (12.4 -> "12.4%" in en-US).

  • currencydefault "USD"
    string

    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).

  • localedefault ""
    string

    Locale for numbers. Empty uses the UIProvider / LocaleProvider locale.

  • showTooltipdefault true
    boolean

    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.

  • animateddefault true
    boolean

    The clockwise sweep and count-up on first view, and gliding slices on data changes. Reduced motion always shows the final state.

  • loadingdefault false
    boolean

    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).

  • labeldefault ""
    string

    Accessible name of the figure (followed by "total ...") and caption of its data table. Empty joins the slice names.

  • descriptiondefault ""
    string

    A one-sentence takeaway for screen readers ("Direct sales bring in 38% of revenue").

  • surfacedefault ""
    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.

Dependencies

motiond3-shapechart-kitmotionui-configuse-controllable-stateuse-locale

Files

  • components/ui/donut-chart.tsx

FAQ

Is the React Donut chart component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Donut chart?

Run npx wingo-ui@latest add donut-chart. It also adds the Wingo UI items it builds on (Chart kit, Motion presets, UIProvider, useControllableState and useLocale) and the npm packages motion and d3-shape. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/donut-chart.

How does Donut chart behave on phones?

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.

See also

Chart kitlibRadial barBar chartArea chart

Used in

KPI Dashboard

More React chart and stat components

Funnel chartLine chartRadar chartTrend badgeGaugeGitHub Activity
All 16 React chart and stat components

Articles using this component

  • React Charts Tailwind: 4 Charts and a Kit for Any Theme

    Component guides · Oct 19, 2026 · 10 min read

PreviousBar chartNext Funnel chart

Revenue by channel

Acme CRM, September 2026, amounts in USD

Revenue by channel, September 2026
SliceValueShare
Direct sales48,92038.1%
Online store36,40028.3%
Partners22,13017.2%
Public tenders13,60010.6%
Other7,4005.8%
Total128,450100%

Tap a slice or a legend row to focus it; tap it again or the hole to clear.

selected: none · tooltip: on · sweep: on