Detent web components
By Shane Logsdon

Charts & metrics

Donut Chart

Implemented

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

semi gauge
empty data (no data state)

Hover a slice or tab to a legend row.

Recorded states

  • hover recorded state; no setter
  • pressed recorded state; no setter
  • reduced motion recorded state; no setter
  • active recorded 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

  • animated
  • aria-label
  • center-label
  • show-legend
  • size
  • thickness
  • variant

Properties

  • centre
  • data
  • formatValue
  • onClick
  • onEnter
  • onLeave
  • value

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.tsx sha256 f6c4845f618f…

Native files

The native module and its artifacts:

  • src/components/donut-chart/donut-chart.ts
  • src/components/donut-chart/styles.css
  • src/components/donut-chart/define.ts
  • docs/components/donut-chart.md
  • site/examples/donut-chart.ts
  • tests/browser/donut-chart.spec.ts
  • evidence/components/donut-chart.md

API

Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.

Attributes

Attributes
AttributePropertyTypeDefaultNotes
animatedanimatedbooleantruePresence semantics: present means true, absent means false.
aria-labelariaLabelstringauto-generatedKebab-case attribute; camelCase property.
center-labelcenterLabelstring"Total"Kebab-case attribute; camelCase property.
show-legendshowLegendbooleantruePresence semantics: present means true, absent means false.
sizesizenumber240Kebab-case attribute; camelCase property.
thicknessthicknessnumber16Kebab-case attribute; camelCase property.
variantvariant"donut" | "semi""donut"Kebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
centre——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
dataDonutDatum[]—Property-only: assign from script; not accepted as an attribute.
formatValue(n: number) => stringIntl.NumberFormatProperty-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

Events
EventUpstream propPayload typeNotes
detent-enteronEnter() => voidComposed, bubbles; fires only for a user action.
detent-leaveonLeave() => voidComposed, bubbles; fires only for a user action.
detent-clickonClick() => voidComposed, 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.
Previous: Tooltip Next: Dot Field

Docs

Components

Project