Acronis UIKit
Components

RadarChart

A typed radar chart for comparing entities across shared quantitative axes.

Usage

import { RadarChart } from '@acronis-platform/ui-react';
import type { ChartConfig } from '@acronis-platform/ui-react';

RadarChart is a typed composition over the shared Chart primitives. Give it data, a per-series config, the series to plot (dataKeys), and the angular axis key (angleKey) — it renders a themed recharts radar (polar grid, spoke labels, tooltip, legend included), so you don't hand-compose recharts children. Its default web uses outerRadius={78} to match the Figma geometry. The legend and value labels do not change the polygon radius; outer labels stay 4px from the polygon vertices.

Series are columns of a shared row (like a bar or line chart), plotted over one polar angular axis. The plot surface is 187px high by default (219px with the legend), and the web is centred on that plot band rather than on the whole box — so the bottom category label clears the legend instead of disappearing behind it. Turning on showLabels widens the gap between the polygon and the category labels, and the box grows with it (240px, or 272px with the legend). Pass a height class via className to override any of it (e.g. className="h-[380px]").

Series colors come from the palette prop, not from config — see Palettes. Each config entry maps a data key to a label and an optional tone that re-points that one series within the palette, resolved into the --color-<key> custom properties the areas stroke and fill from.

Examples

Two series compared across shared axes, with a themed tooltip and legend:

const data = [
  { subject: 'Math', alice: 120, bob: 110 },
  { subject: 'English', alice: 86, bob: 130 },
  { subject: 'Physics', alice: 85, bob: 90 },
  { subject: 'History', alice: 65, bob: 85 },
];

const config = {
  alice: { label: 'Alice' },
  bob: { label: 'Bob' },
} satisfies ChartConfig;

<RadarChart
  config={config}
  data={data}
  dataKeys={['alice', 'bob']}
  angleKey="subject"
  className="h-[380px] w-[420px]"
/>;

Draw the web as smooth circles instead of straight polygon rings with gridType="circle":

<RadarChart
  config={config}
  data={data}
  dataKeys={['alice', 'bob']}
  angleKey="subject"
  gridType="circle"
/>

The chart shows data-point dots by default, matching the Figma design. Tune the areas with fillOpacity / strokeWidth / showDots (sized by dotRadius), and toggle the chrome with showGrid / showTooltip / showLegend. The legend is always rendered below the chart.

The value scale

A radar has no scale of its own by default: recharts stretches the web so the largest value in the data touches the outer ring, which makes an area readable only against its neighbours — two charts of the same metric aren't comparable. showRadiusAxis draws the scale, and radiusAxisDomain="fixed" with radiusAxisDomainMax pins the outer ring to the metric's own maximum, so the areas read as absolute profiles:

<RadarChart
  config={config}
  data={data}
  dataKeys={['alice', 'bob']}
  angleKey="subject"
  showRadiusAxis
  radiusAxisAngle={60}
  radiusAxisDomain="fixed"
  radiusAxisDomainMax={150}
  radiusAxisTickCount={4}
/>

radiusAxisAngle rotates the scale. Point it between two spokes: along one, its outermost tick lands on that category's own label. The ticks sit over the areas at any angle — a radar's plot area is its areas — so the angle buys clearance from the category labels, not from the fills. radiusAxisOrientation picks the side its labels sit on, and radiusAxisTickCount sets how many ticks it asks for — which is also how many rings the grid draws, shown or not.

radiusAxisDomainMax has to be positive: a 0 or negative maximum can't bound a scale growing outward from 0, so it falls back to the data's own top rather than collapsing the web.

The domain applies whether or not the scale is drawn, so a chart can be scaled to a known maximum without showing the ticks:

<RadarChart
  config={config}
  data={data}
  dataKeys={['alice']}
  angleKey="subject"
  radiusAxisDomain="fixed"
  radiusAxisDomainMax={150}
/>

For a metric where less is better (latency, error rate), radiusAxisReversed puts the maximum at the centre so a good profile still reads as a large area.

Grid and angle axis

radialLines={false} drops the web's spokes and keeps its concentric rings. The categorical axis around the web is configurable too — angleAxisOrientation moves its labels inside the web, angleAxisLineType matches its outline to a circular grid, and angleAxisLine / angleTickLine / angleTickSize control the rest:

<RadarChart
  config={config}
  data={data}
  dataKeys={['alice', 'bob']}
  angleKey="subject"
  gridType="circle"
  angleAxisLineType="circle"
  angleAxisOrientation="inner"
  angleTickLine={false}
/>

showAngleAxis={false} hides all of that chrome at once. The axis itself stays — it's also what names each category — so the tooltip keeps its header.

Geometry

cx / cy move the centre, startAngle / endAngle rotate the sweep, innerRadius / outerRadius size the web (an inner radius separates the series where they all bottom out), and margin insets the plot area:

<RadarChart
  config={config}
  data={data}
  dataKeys={['alice', 'bob']}
  angleKey="subject"
  innerRadius={40}
  outerRadius="70%"
  startAngle={45}
  endAngle={-315}
/>

Passing margin replaces all four sides — a side you leave out is 0, not the default 5 — so name every side you need.

Per-series styling

seriesSettings overrides the chart-level styling for one series, keyed by its dataKeys entry — color, stroke, fillOpacity, strokeWidth, dot, dotRadius, and activeDot. Every series not named keeps the chart-level values, and an overridden color carries into that series' legend swatch and tooltip dot — as does stroke on its own, which recharts reads first, so set color too if the marker should keep following the fill:

<RadarChart
  config={config}
  data={data}
  dataKeys={['alice', 'bob']}
  angleKey="subject"
  seriesSettings={{
    alice: { fillOpacity: 0.05, strokeWidth: 3, dot: true, dotRadius: 4 },
  }}
/>

Every plotted series needs a value in every row. recharts places a missing one at the centre of the web rather than leaving a hole, so it reads as a zero — drop the whole category row instead of leaving a series' value out of it.

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 { RadarChart, ChartTooltipContent } from '@acronis-platform/ui-react';

<RadarChart
  config={config}
  data={data}
  dataKeys={['alice', 'bob']}
  angleKey="subject"
  tooltipContent={
    <ChartTooltipContent
      formatter={(value, name) => `${name}: ${value.toLocaleString()}`}
    />
  }
/>;

Animation

Charts render statically by default. Opt in to an entrance animation with animate, and tune it with animationDuration / animationBegin / animationEasing:

<RadarChart
  config={config}
  data={data}
  dataKeys={['alice']}
  angleKey="subject"
  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:

<RadarChart
  config={config}
  data={data}
  dataKeys={['alice']}
  angleKey="subject"
  showLabels
  labelFormatter={formatCompactNumber}
/>

When value labels are on, the category labels around the web are automatically pushed further out so the two don't overlap at the top vertex. Pass an explicit angleTickSize to override.

labelPosition moves them; the default is top, which sits each value above its vertex on the chart surface. An inside* position draws it over the area instead. An area is a translucent fill, so what sits behind the label there is the surface tinted by the series color rather than the color itself — the label keeps the theme-inverting on-surface token, which stays legible over the tint in both themes:

<RadarChart
  config={config}
  data={data}
  dataKeys={['alice']}
  angleKey="subject"
  showLabels
  labelPosition="insideEnd"
/>

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

RadarChart sets no role and no aria-* of its own, and the SVG the web is 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 RadarChart 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.

<RadarChart
  role="img"
  aria-label="Scores by subject — Alice: Math 120, English 86, Physics 85, History 65. Bob: Math 110, English 130, Physics 90, History 85."
  config={config}
  data={data}
  dataKeys={['alice', 'bob']}
  angleKey="subject"
/>

Either way, a name is not a text alternative. A radar 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 series in the document. The spoke labels the angle axis draws — and the radius-axis ticks when showRadiusAxis is on, and the data labels when showLabels is — are real <text> rather than pixels, but they sit in the SVG with no structural role, so a category and its value are never associated with each other there.

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. The web's startAngle / endAngle sweep and the order the categories are placed in don't depend on the pin — an angle is not a reading direction.

The labels around the web do. PolarAngleAxis anchors each spoke label with the direction-relative SVG keywords text-anchor: start / end, chosen from which side of the centre the spoke falls on, and PolarRadiusAxis does the same for its ticks when showRadiusAxis is on. Without the pin an inherited dir="rtl" would mirror each of those across its own anchor and push it back over the web. The spoke labels at the very top and bottom are anchored middle and would not move, which is what makes the failure look like a partial one. The showLabels values sit at labelPosition="top" by default, also middle, and are direction-immune either way. The pin stops at the surface, so the legend and the tooltip — HTML outside it — still mirror with the page.

API Reference

Prop

Type

Edit on GitHub

On this page