Charts & metrics
Donut Chart
Animated SVG donut and gauge chart with a live center value
detent-donut-chart
Port status
- Catalog status
- implemented
- Origin
- upstream
- Phase
- 3
- Wave
- phase-3-data
- Group
- Charts & metrics
6.2 KB gzip for the class module
Variants
Hover a slice or tab to a legend row.
Recorded states
hoverrecorded state; no setterpressedrecorded state; no setterreduced motionrecorded state; no setteractiverecorded state; no setter
Usage
Import
import '/components/donut-chart/define.js';
// or register it yourself
import { DetentDonutChart } from '/components/donut-chart/donut-chart.js';
Attributes
animatedaria-labelcenter-labelshow-legendsizethicknessvariant
Properties
centredataformatValueonClickonEnteronLeavevalue
Slots
No slots are recorded for this component.
Events
detent-enter— {}detent-leave— {}detent-click— {}
Example
Runnable example: site/examples/donut-chart.ts. The snippet below is generated from the catalogued attributes.
<detent-donut-chart
animated
aria-label="auto-generated"
center-label="Total"
show-legend
size="240"
thickness="16"
variant="donut"
></detent-donut-chart>
Property-only values
const element = document.querySelector('detent-donut-chart');
if (element !== null) {
// data: DonutDatum[]
element.data = [];
// formatValue: (n: number) => string
element.formatValue = () => {};
}
Source
The pinned upstream source for this row:
src/components/ui/DonutChart.tsxsha256 f6c4845f618f…
Native files
The native module and its artifacts:
src/components/donut-chart/donut-chart.tssrc/components/donut-chart/styles.csssrc/components/donut-chart/define.tsdocs/components/donut-chart.mdsite/examples/donut-chart.tstests/browser/donut-chart.spec.tsevidence/components/donut-chart.md
API
Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.
Attributes
| Attribute | Property | Type | Default | Notes |
|---|---|---|---|---|
animated | animated | boolean | true | Presence semantics: present means true, absent means false. |
aria-label | ariaLabel | string | auto-generated | Kebab-case attribute; camelCase property. |
center-label | centerLabel | string | "Total" | Kebab-case attribute; camelCase property. |
show-legend | showLegend | boolean | true | Presence semantics: present means true, absent means false. |
size | size | number | 240 | Kebab-case attribute; camelCase property. |
thickness | thickness | number | 16 | Kebab-case attribute; camelCase property. |
variant | variant | "donut" | "semi" | "donut" | Kebab-case attribute; camelCase property. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
centre | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
data | DonutDatum[] | — | Property-only: assign from script; not accepted as an attribute. |
formatValue | (n: number) => string | Intl.NumberFormat | Property-only: assign from script; not accepted as an attribute. |
onClick | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
onEnter | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
onLeave | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
value | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
Events
| Event | Upstream prop | Payload type | Notes |
|---|---|---|---|
detent-enter | onEnter | () => void | Composed, bubbles; fires only for a user action. |
detent-leave | onLeave | () => void | Composed, bubbles; fires only for a user action. |
detent-click | onClick | () => void | Composed, bubbles; fires only for a user action. |
Slots
No slots are recorded for this component.
CSS customisation
- `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- className is replaced by the documented CSS parts and custom properties; consumers need no Tailwind.
- Framer Motion springs become WAAPI stroke-width growth plus a requestAnimationFrame count-up; both stop on disconnect and are skipped under reduced motion.
- React state/hooks are replaced by element-owned state with pre-upgrade property preservation.
- Light-scheme slice colours are darkened from upstream CHART_COLORS so each data mark keeps ≥ 3:1 against the ring surface (PC-12.6 accessibility correction).
- The centre readout is text, not an animated number element; it is not a live region because the legend already exposes the same values.
- formatValue is a property-only function hook; there is no string form and no attribute fallback.
- Slices are pointer-only; keyboard access goes through the legend buttons, so there is no roving tabindex inside the SVG.
What it does
An SVG donut or semi-circle gauge with arcs that grow in, a centre readout that counts up to the total and switches to the active slice, and a legend that filters slices while keeping at least one visible. Zero charting dependencies.
Upstream behaviour
- Full donut and semi-circle gauge variants from the same component
- Spring-animated arcs that grow in on mount and stretch on hover or focus
- Animated center number that counts up to the total and switches to the hovered slice
- Interactive legend toggles slices while keeping at least one visible
- Automatically holds the legend beside the ring on wide containers and below it on mobile
- Zero charting library dependencies with a fully responsive SVG
- Honors prefers-reduced-motion for arcs, center number, and readout transitions
Differences from upstream
- `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- className is replaced by the documented CSS parts and custom properties; consumers need no Tailwind.
- Framer Motion springs become WAAPI stroke-width growth plus a requestAnimationFrame count-up; both stop on disconnect and are skipped under reduced motion.
- React state/hooks are replaced by element-owned state with pre-upgrade property preservation.
- Light-scheme slice colours are darkened from upstream CHART_COLORS so each data mark keeps ≥ 3:1 against the ring surface (PC-12.6 accessibility correction).
- The centre readout is text, not an animated number element; it is not a live region because the legend already exposes the same values.
- formatValue is a property-only function hook; there is no string form and no attribute fallback.
- Slices are pointer-only; keyboard access goes through the legend buttons, so there is no roving tabindex inside the SVG.