Radial bar React chart component for Tailwind CSS v4
v1.0.0Added in 1.0.0
A React radial bar chart: several goals as concentric activity rings or 270° bars, each filling toward its own target and lapping past 100%.
The first time the chart scrolls into view the rings sweep in one after another on a soft spring; a ring past its target keeps going for a second lap, its end cap casting a small shadow on the lap below. Hover a ring (or a legend row) and the others dim while the hole shows that ring's percent and name; when the hole is too small, a readout floats beside the ring's end instead. Click or tap a ring or a legend row to select it: it stays lit (even while you preview another ring) and the center keeps its readout; tap it again, or tap the hole, to clear. On a phone the ring nearest your finger wins (thin rings are easy to hit) and the legend sits below the chart under 480px of width. With the keyboard, Tab to the chart: the arrow keys walk the rings (Up / Left outward, Down / Right inward), Home / End jump to the outermost and innermost ring, Enter or Space selects, Escape clears; a live region reads each ring. The legend rows are buttons in the tab order after the chart. When the values change, the rings glide to their new length.
Radial bar is a Pro React chart 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 radial-bar, 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 radial-barRadial bar 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 RadialBar from @/components/ui/radial-bar.
import { RadialBar } from "@/components/ui/radial-bar"
const goals = [
{ id: "calls", label: "Calls", value: 42, max: 60 },
{ id: "meetings", label: "Meetings", value: 7, max: 10 },
{ id: "quotes", label: "Quotes sent", value: 12, max: 15 },
]
export function TeamActivity() {
return (
<RadialBar
data={goals}
label="Team activity today"
center={<span className="text-xl font-semibold">3 goals</span>}
onValueChange={(id) => console.log(id)}
/>
)
}Props
data*RadialBarDatum[] = { id: string; label: ReactNode; value: number; max?: number; color?: string; name?: string }[]One ring per item, outermost first. max is the ring's own target (the chart's max when left out); a value past it laps. Negative values draw as 0; a max of 0 or less leaves the ring empty.
maxdefault100numberThe target for items without their own max. 100 makes value a percentage.
variantdefault"rings""rings" | "bars"rings: full 360° activity rings with round caps. bars: 270° radial bars from 12 o'clock, each named just left of its start.
sizedefault"md""sm" | "md" | "lg" | numberDiameter 120 / 180 / 240px, or any number of px (a 52px ring for a list row). It shrinks with a narrower container and sets the center readout's type size.
thicknessdefault0numberRing width in px. 0 fits every ring with the gap and keeps about 46% of the radius as the hole (32% for bars), never under 6px and never past 22% of the radius.
gapdefault4numberPx of space between rings.
showTrackdefaulttruebooleanEach ring's full track in its own color at 15%, so the remaining distance to the target is visible.
roundCapsdefaulttruebooleanRounded ring ends. Laps only cast their end-cap shadow with round caps.
showLabelsdefaulttruebooleanbars variant: the ring names right-aligned just left of 12 o'clock. They hide on their own when the rings are too close for 11px text.
showLegenddefaulttruebooleanThe list of rings with values and percentages; its rows select a ring.
legendPositiondefault"auto""auto" | "right" | "bottom"auto: beside the chart from 480px of container width, below it on narrower containers (decided by layout, so the server HTML is already right).
showValuesdefaulttruebooleanThe "42 / 60" value and target in each legend row.
showPercentdefaulttruebooleanThe percent of target in each legend row, in a fixed-width column.
centerReactNodeContent in the hole ("3 obiective"). While a ring is hovered, focused or selected, its readout (percent, name, and "42 / 60" on large holes) takes the place with a short cross-fade.
valuestring | nullThe selected ring id when you control the selection; pair with onValueChange. The other rings dim and the center shows it.
defaultValuedefault"null"string | nullThe id of the ring selected at first, uncontrolled; null starts with nothing selected. The Laps past 100% example starts on Quotes sent.
onValueChange(id: string | null) => voidFires when a ring is selected from the chart, the legend or the keyboard, and with null when cleared.
valueFormatdefault"number"ChartValueFormat = "number" | "compact" | "currency" | "percent"How values and targets print in the legend, readouts and table ("$128,450"). Percent takes whole numbers (67.6 -> "67.6%"). The progress percent is always a percent.
currencydefault"USD"stringISO 4217 code for valueFormat currency, printed with the narrow symbol ("$190,000").
decimalsdefault0numberFraction digits for values and targets; auto by magnitude when unset (Auto in the playground).
formatValue(value: number, context: { id: string; part: "value" | "max"; kind: "legend" | "tooltip" | "center" | "table" | "aria" }) => stringFull override for printing values and targets, per ring (dollars for one ring, a plain count for another).
localedefault""stringLocale for numbers and percents. Empty uses the UIProvider / LocaleProvider locale.
showTooltipdefaulttruebooleanA floating readout of the inspected ring when the hole is too small to show it (bars with many rings, size sm). The keyboard and screen reader readout stays on when false.
tooltipModedefault"auto""auto" | "float" | "fixed"float: beside the ring's end. fixed: pinned above the chart. auto: fixed on touch screens, float with a mouse.
animateddefaulttruebooleanRings sweep in on first view (laps included) and glide when values change. Reduced motion always shows the final state.
loadingdefaultfalsebooleanA ring-shaped skeleton in the chart's box; the legend is hidden.
emptyStateReactNodeReplaces the built-in empty state, shown when data is empty.
labeldefault""stringAccessible name of the figure and caption of its data table. Empty joins the ring names.
descriptiondefault""stringA one-sentence takeaway for screen readers ("Two of three goals are above 70%").
labelsPartial<RadialBarLabels>Strings: empty, loading, inspectHint, ring (Goal), value, target, progress, selected, and valueText(value, max, percent) for "42 of 60, 70%".
classNamesPartial<Record<RadialBarSlot, string>>Classes for legend, legendItem, svg, ring (the group), track, fill, lap, label, center, tooltip, table and inspect.
classNamestringExtra classes for the figure (the root), merged last.
Parts
DEFAULT_RADIAL_BAR_LABELS
constantThe English copy: the shared chart labels plus inspectHint, ring, value, target, progress, selected and valueText(value, max, percent). Spread it into labels when you translate only a few keys.
RadialBarDatum
typeOne ring: { id, label, value, max?, color?, name? }. max falls back to the chart's max; color to the chart palette.
Examples
Laps past 100%
Quotes sent 18 of 15: a second lap with a shadowed end cap.
Bars variant
270° radial bars for five reps, percent of the monthly target.
Sizes
sm in a list row, md and lg on their own.
Controlled selection
A segmented control and the rings share one selected goal.
Tone colors
success / warning / danger by attainment, dollars for one ring.
On a phone
Under 480px the legend moves below the rings.
Loading, zero and empty
The skeleton, tracks only, and the empty state.
Files
- components/ui/radial-bar.tsx
FAQ
Is the React Radial bar chart component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Radial bar?
Run npx wingo-ui@latest add radial-bar. 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/radial-bar.
How does Radial bar behave on phones?
On a phone the ring nearest your finger wins (thin rings are easy to hit) and the legend sits below the chart under 480px of width.
See also
More React chart and stat components
Articles using this component
- React Gauge Chart: Zones, Targets and Small Sizes
Component guides9 min read