Skip to main content

RingChart

Part to Wholechart

A single value as a filled ring (a progress or share reading) with the number in the centre.

import { RingChart } from "@elabs-ai/components-charts";
Install
npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-charts

Skipping the @source line is the most common first failure: Tailwind does not scan node_modules, so every component renders unstyled. The one-time wiring

Prompt for your coding agent

Paste it into Claude Code, Cursor, Codex or any other coding agent. Text in [brackets] is yours to fill in.

Add the brand-ui RingChart chart (from @elabs-ai/components-charts) to this project.

1. Read https://elabs-ai.com/llms.txt first: it maps every package and the rules. If you can use MCP servers, connect https://elabs-ai.com/mcp and use its search, docs and tokens tools. If not, run the same lookups in the terminal: `npx -y @elabs-ai/components-cli search "<what you need>"` and `npx -y @elabs-ai/components-cli docs <Component>`.
2. Look RingChart up before writing code: `npx -y @elabs-ai/components-cli docs RingChart` prints its real props, variants, what it pairs with and what to avoid.
3. If @elabs-ai/components-charts is not installed yet: `pnpm add @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-charts` (or `npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-charts`). If this is the first brand-ui package in the project, wire it once: import `@elabs-ai/components-tokens/styles.css` plus the `themes/light.css` and `themes/dark.css` stylesheets in the CSS entry, add one Tailwind `@source` line per installed @elabs-ai package (Tailwind does not scan node_modules, so without it everything renders unstyled), and wrap the app root in `ThemeProvider` from `@elabs-ai/components-tokens`.
4. Use it here: [the screen or file, and what it should show]
5. Rules: use only @elabs-ai/components-* components and the semantic tokens — no raw colours, no raw font sizes, no arbitrary Tailwind values. Look up each component's real props with the docs tool before you use it; never guess a prop.
6. Done means: the typecheck passes, `npx -y @elabs-ai/components-cli audit src --strict` reports nothing, and you have opened the app and looked at every screen you touched in the light and the dark theme.

Use it for

  • What is it made of? Several progress values.
  • one proportion against its maximum, read as a single ring

Avoid

  • several categories matter — use a pie or unit chart

Examples

8 live examples

Default

Loading

Loading skeleton (RM-183) — shown while `status="loading"`, sized like the real chart.

Fixed Size

Fixed pixel size — bypasses ParentSize and uses a concrete dimension.

With Accessible Label

Accessible variant — announces label + description to screen readers on focus.

Partial Fill

Fewer rings at different fill levels.

Drilldown

Click a ring — or Tab in and press Enter — to drill into it.

Tick Ring

Selection states

API

RingChart

PropTypeDescription
datarequiredRingData[]Data array - each item represents a ring
sizenumberChart size in pixels. If not provided, uses parent container size
plotHeightResponsive<ChartPlotHeight>The plot's own height (ADR 0039): px, or `{ aspect }` (width ÷ height), optionally per breakpoint.
strokeWidthnumberStroke width of each ring. Default: 12
ringGapnumberGap between rings. Default: 6
baseInnerRadiusnumberInner radius of the innermost ring. Default: 60
animationDurationnumberHow long each ring's enter animation (the ring growing in, then its progress arc sweeping) runs, in milliseconds. The gaps between rings are set by `enterStaggerScale`, not by this. Ignored when `enterTransition` is set. Default: 1100
classNamestringAdditional class name for the container
hoveredIndexnumber | nullControlled hover state - index of hovered ring
onHoverChange(index: number | null) => voidCallback when hover state changes
startAnglenumberStart angle in radians. Default: -PI/2 (top)
endAnglenumberEnd angle in radians. Default: 3*PI/2 (full circle)
enterTransitionTransitionFramer Motion transition for ring enter animation. Wins over `animationDuration`.
enterStaggerScalenumberScales ring stagger delays (1 = default).
geometryScrubbingbooleanHigh-frequency geometry updates (e.g. studio NumberField scrub). Uses plain SVG paths instead of Motion `d` morphing.
childrenrequiredReactNodeChild components (Ring, RingCenter, etc.)
onDatapointClickChartDatapointClickHandlerDrill-down (#349). Fires when a ring is activated by pointer OR keyboard. Setting it mounts a keyboard-operable target layer OUTSIDE the aria-hidden SVG — one tab stop, arrow keys to traverse. Unset changes nothing.
copyValueOnActivatebooleanPut the datapoint's exact value on the clipboard when it is activated — the recovery path for a compact axis label. Default `false`; a chart with no interaction props still renders byte-identical DOM. A consumer-supplied `onDatapointClick` always wins.
datapointLabelChartDatapointLabelOverride the accessible name of each keyboard drill-down target (#349).
maxInteractiveDatapointsnumberDev-warning threshold on the number of keyboard targets. Default 500 (#349).
accessibleLabelChartA11yProps["accessibleLabel"]Accessible name for the chart region (announces to AT on focus).
accessibleDescriptionChartA11yProps["accessibleDescription"]Supplemental description read by AT (e.g. ring names + values).
labels"outside" | { placement?: "outside" | "none" }Outside-label mode for the high-decoration tick ring (lieflat F4 "Tick Donut", #RM-030). `"outside"` draws a dotted leader line from each segment's ticks to its label, placed outside the ring, and reserves layout room for it. Only takes visible effect at `--decoration` ≥ 8 (the smooth-arc rendering below that threshold has no leader/label treatment); unset renders exactly as before. Also accepts `PieChart`'s `labels` OBJECT shape (RM-114 API parity only — `{ placement: "outside" }` maps to this same tick-ring mode; every other `placement`/`show`/`matchColor`/`minAngle` field is a no-op here, `RingChart` has no inside-label or leader-collision treatment of its own). `{ placement: "none" }` and every other object shape behave as unset.
paletteChartPaletteColour ramp for the rings (RM-186), through `resolvePalette`; an item's own `color` still wins. Unset: the twelve categorical colours, cycled, as before.
selectionStatesChartSelectionStatesResolver<Record<string, unknown>>Maps a mark (category, optional series key, optional raw row) to its selection state. Unset → the chart renders exactly as before.
dimExcludedbooleanDim `excluded` marks and add their non-hue channel (the dashed frame). Default `true`; `false` keeps the `data-selection` attribute (so a host can style it) but paints nothing.
marginnumber | Partial<Margin>Space around the plot, in pixels: one number for every side, or per side.
statusChartStatusShow the loading skeleton until the data is ready.
emptyChartEmptyStateTitle and message shown when there is nothing to plot.

Also accepts: ChartSelectionProps, Pick<FrameSizeGroupProps, "margin">, Pick<ChartStateGroupProps, "status" | "empty">

Ring

PropTypeDescription
indexrequirednumber
colorstring
animateboolean
showGlowboolean
lineCapRingLineCap

RingCenter

PropTypeDescription
defaultLabelstringLabel shown below the value. Default: "Total" when not hovering
formatOptionsChartStatFlowFormatFormat options for NumberFlow. Default: standard notation
children(props: { value: number; label: string; isHovered: boolean; data: { label: string; value: number; maxValue: number; color?: string }; }) => ReactNodeCustom render function for complete control over center content
classNamestringAdditional class name for the container
valueClassNamestringClass name for the value text. Scales with center size via container queries.
labelClassNamestringClass name for the label text. Scales with center size via container queries.
prefixstringPrefix to show before the number (e.g., "$")
suffixstringSuffix to show after the number (e.g., "%")