RadialBarChart
A typed radial bar chart — concentric arcs or a gauge.
Usage
import { RadialBarChart } from '@acronis-platform/ui-react';
import type { ChartConfig } from '@acronis-platform/ui-react';RadialBarChart is a typed composition over the shared Chart
primitives. Give it data, a per-arc config, the value field (dataKey), and
the label field (nameKey) — it renders a themed recharts radial bar chart
(concentric arcs, an optional background track, tooltip, and legend), so you
don't hand-compose recharts children.
Each data row becomes a concentric arc (like a pie's slices unrolled into rings).
Arc colors come from the palette prop, not from config — see
Palettes. Each config entry, keyed by a
nameKey value, maps that arc to a label and an optional tone that
re-points it within the palette. RadialBarChart resolves them into the
--color-<name> custom properties its arcs fill from.
Examples
Concentric arcs sweeping a full circle, with a background track, tooltip and legend:
const data = [
{ browser: 'Chrome', value: 65 },
{ browser: 'Safari', value: 50 },
{ browser: 'Firefox', value: 35 },
{ browser: 'Edge', value: 25 },
];
const config = {
Chrome: { label: 'Chrome' },
Safari: { label: 'Safari' },
Firefox: { label: 'Firefox' },
Edge: { label: 'Edge' },
} satisfies ChartConfig;
<RadialBarChart
config={config}
data={data}
dataKey="value"
nameKey="browser"
className="h-[360px] w-[360px]"
/>;Make a half-circle gauge with startAngle / endAngle:
<RadialBarChart config={config} data={data} dataKey="value" nameKey="browser" startAngle={180} endAngle={0} />The order of the two angles is the sweep's direction — 90 → -270 runs clockwise,
-270 → 90 counter-clockwise.
Tune the radii (innerRadius / outerRadius), cornerRadius, and toggle the
showBackground track / showTooltip / showLegend. showPolarGrid adds the
concentric grid behind the arcs, and cx / cy / barSize / barGap /
barCategoryGap / margin place and size the ring itself.
Gauges
A gauge needs a scale. Without valueDomain, every arc is drawn relative to
the largest value in the data — so a single value always fills the whole sweep,
which is never what a gauge means. Give it the metric's own range and the arc
becomes that fraction of the sweep, over the showBackground track:
<RadialBarChart
config={{ Used: { label: 'Used' } }}
data={[{ metric: 'Used', value: 65 }]}
dataKey="value"
nameKey="metric"
valueDomain={[0, 100]}
startAngle={180}
endAngle={0}
innerRadius={80}
outerRadius={130}
cy={190}
centerLabel={{ value: '65%', label: 'of quota used' }}
showLegend={false}
className="h-[240px] w-[360px]"
/>centerLabel puts the readout in the hole — a headline value and/or a label
caption. Give innerRadius enough room to hold the text; it isn't clipped to the
hole. For a half sweep, cy moves the baseline down so the drawn half fills its
box.
minAngle floors a tiny arc (in degrees) so it stays visible and hoverable. The
floor applies to a 0 too, which then reads as a small positive value — leave it
unset when a metric can legitimately be zero.
Segmented gauge
segments cuts a single-value gauge's ring into equal segments, notched apart by
segmentGap degrees:
<RadialBarChart
config={{ criteria: { label: 'Criteria met' } }}
data={[{ criteria: 'criteria', value: 29 }]}
dataKey="value"
nameKey="criteria"
valueDomain={[0, 38]}
segments={8}
segmentGap={4}
innerRadius={88}
outerRadius={120}
centerLabel={{ value: 29, label: '/ 38 criteria met' }}
className="h-[360px] w-[360px]"
/>The segments up to the value take the arc's color and the rest the muted track, so
showBackground plays no part. The notches are geometry rather than data rows,
which is why the arc labels and the legend are suppressed — so centerLabel is
the chart's only always-visible reading here, not decoration. Give it the value
and its scale. The tooltip stays and reports the metric (with the valueDomain
maximum when there is one) wherever on the ring it's hovered, but a hover-only
tooltip can't be the only place a value exists.
Segments only apply to a single-value gauge. Anything else falls back to the
normal concentric arcs, legend included, rather than erroring: more than one row,
dataKeys set, fewer than two segments, or a dataKey value that isn't a number
(a stringified "29" off an API).
A valueDomain that can't place a value — no span ([n, n]) or inverted
([max, min]) — draws the ring as all track. A gauge that can't read its scale
reports no progress rather than guessing at one.
Multi-metric
dataKeys plots one arc per metric instead of one per row. Colors and legend
labels then come from config keyed by the metric key — the same convention as
the cartesian charts — while nameKey names the band they share:
<RadialBarChart
config={{
used: { label: 'Used' },
quota: { label: 'Quota' },
}}
data={[{ tier: 'Production', used: 72, quota: 90 }]}
dataKeys={['used', 'quota']}
dataKey="used"
nameKey="tier"
valueDomain={[0, 100]}
startAngle={180}
endAngle={0}
cy={190}
className="h-[260px] w-[380px]"
/>Every metric shares one angular scale, so pass valueDomain when they're measured
against a known maximum — otherwise they're scaled against the largest value
present and the biggest arc always looks complete.
Custom tooltip
Replace the tooltip with a configured ChartTooltipContent — imported from the
same library, so you never compose recharts yourself. Its formatter renders
each row and labelFormatter the header, giving you custom formatting, per-series
content, and extra fields:
import { RadialBarChart, ChartTooltipContent } from '@acronis-platform/ui-react';
<RadialBarChart
config={config}
data={data}
dataKey="value"
nameKey="browser"
tooltipContent={
<ChartTooltipContent
nameKey="browser"
hideLabel
formatter={(value, name) => `${name}: ${value.toLocaleString()} users`}
/>
}
/>;Animation
Charts render statically by default. Opt in to an entrance animation with
animate, and tune it with animationDuration / animationBegin /
animationEasing:
<RadialBarChart config={config} data={data} dataKey="visitors" nameKey="browser" animate animationDuration={800} animationEasing="ease-out" />animate honors prefers-reduced-motion: for a visitor who has asked their
system to reduce motion, the series render at their final geometry with no
animation (the same applies when rendering on the server).
Data labels
Annotate each point with its value using showLabels. labelFormatter takes the
same formatters as the axes, so a label and its axis read alike:
<RadialBarChart config={config} data={data} dataKey="visitors" nameKey="browser" showLabels labelFormatter={formatCompactNumber} />Labels sit insideStart by default — inside the arc, in a token that stays legible
over a saturated series colour. labelPosition takes the polar placements:
outside, center, centerTop, centerBottom, insideStart, insideEnd,
end; outside moves them onto the chart surface instead:
<RadialBarChart config={config} data={data} dataKey="visitors" nameKey="browser" showLabels labelPosition="outside" />labelFormat decides what a label reads — value (the default) or name-value,
which prefixes the arc's name (its nameKey value, or the metric's config label
in multi-metric mode). A name-value label is long and recharts curves it along its
arc, so give the innermost arc enough circumference to hold it:
<RadialBarChart config={config} data={data} dataKey="visitors" nameKey="browser" showLabels labelFormat="name-value" innerRadius={70} />Labels are off by default — with more than one series they collide easily, so turn them on for a single series or a chart with room to spare.
Accessibility
RadialBarChart sets no role and no aria-* of its own, and the SVG the arcs are
drawn into has no accessible name. That gap belongs to this component rather than
recharts: recharts takes title, desc and role as first-class chart props and
writes them straight into the plot's <svg>, but RadialBarChart passes none of
them down — it spreads the props you give it onto its own root <div> and hands
the recharts chart only the data it computes itself, so the <title> / <desc> pair
recharts always emits stays empty. recharts' accessibility layer is on by default
and is not switched off here, so the plot is also a tab stop carrying
role="application": a focus stop that announces neither a name nor any data. The
component exposes no way to turn that off.
So put the name on the root, which spreads the props you pass. Choose the role
before copying the snippet: role="img" names the chart but also makes its subtree
presentational, and it does not remove the focusable plot inside it — wrapping the
chart in a <figure> with a <figcaption> is often the better fit here.
aria-labelledby works the same way as aria-label.
<RadialBarChart
role="img"
aria-label="Share of sessions: Chrome 65, Safari 50, Firefox 35, Edge 25"
config={config}
data={data}
dataKey="value"
nameKey="browser"
/>Either way, a name is not a text alternative. A ring of arcs is a visual encoding of
numbers, so the numbers themselves have to be reachable as text — a caption, a
summary sentence, or a table beside the chart. The legend and the tooltip are
ordinary HTML laid out beside the SVG rather than inside it, so the legend already
names every arc in the document; the arc labels (showLabels) and the centerLabel
readout are real <text> rather than pixels, but they sit in the SVG with no
structural role. A segmented gauge is the case to watch: it suppresses both the
arc labels and the legend, so the only reading it renders is centerLabel inside the
SVG and a hover-only tooltip — give the value and its scale a home in text outside
the chart as well.
Direction (RTL)
Under dir="rtl" the chrome around the plot — the legend, the tooltip, and any
readouts you compose alongside them — mirrors on its own, because it is ordinary
flow layout built from logical CSS utilities. The plot area deliberately stays
left-to-right: recharts places every mark in physical SVG coordinates, so mirroring
it would flip the reading order of the data without flipping the geometry that
encodes it. ChartContainer holds that in place by pinning .recharts-surface to
direction: ltr. Most of this chart doesn't need the pin: the sweep from
startAngle to endAngle keeps its direction whatever the page does, a segmented
ring's notches are geometry, the angular axis draws no ticks, and the centerLabel
is anchored middle.
The arc labels are the part that does. showLabels draws each one along a
<textPath> following its arc, with no anchor set, so it takes SVG's default
text-anchor: start — which is direction-relative, and under an inherited
dir="rtl" would start the text from the other end of the arc. The pin keeps them
running from the start of the sweep. It stops at the surface, so the legend and the
tooltip — HTML outside it — still mirror with the page.
API Reference
Prop
Type