Radar Chart
Token-driven radar (spider) chart built on Recharts v3 for comparing multiple metrics across one or more series, with full light/dark theming.
Playground
Installation
pnpm add @tessinaui/ui rechartsUsage
import { RadarChart } from "@tessinaui/ui";
import type { ChartConfig } from "@tessinaui/ui";
const config: ChartConfig = {
productA: { label: "Product A", color: "var(--chart-1)" },
productB: { label: "Product B", color: "var(--chart-3)" },
};
const data = [
{ metric: "Speed", productA: 120, productB: 90 },
{ metric: "Reliability", productA: 98, productB: 130 },
{ metric: "Comfort", productA: 86, productB: 130 },
];
<RadarChart data={data} config={config} categoryKey="metric" />Examples
Default
A single-series radar comparing one product across six metrics.
Multiple series
Two radars overlaid in distinct brand tones, with a legend.
Sizes
Three square size caps — sm, md, and lg.
Lines only
Outline-only radar with fillOpacity={0} so the shape reads as a stroke.
Grid types
Four grids: concentric polygons (default) or circles via gridType, rings
without spokes via radialLines={false}, and no grid at all.
Radius axis and a pinned scale
showRadiusAxis puts the value scale on the chart, and maxValue pins the
outer ring. Without it the outer ring is the largest value in your data, so a
dataset topping out at 72 paints a full-radius polygon and reads as a perfect
score.
Values on the axis labels
Pass a PolarAngleAxis through children to take over the axis labels —
Recharts keys polar axes by id, so yours replaces the chart's own rather than
drawing a second one on top.
Icon labels
The same seam, with a decorative glyph above each axis name. Keep the name as text: an icon alone is not readable to assistive tech or at 200% zoom.
Filtering series
series is the filter — pass the keys you want plotted and the chart draws
exactly those. No extra API is needed for a rater or category picker.
States
The loading skeleton alongside the empty state.
Core props
| Prop | Type | Default | Description |
|---|---|---|---|
data | Record<string, string | number | null>[] | — | One row per axis (spoke). |
config | ChartConfig | — | Series metadata (label, color). |
categoryKey | string | — | Datum key for the angular axis. |
series | string[] | all config keys | Which series to plot. |
fillOpacity | number | 0.1 | Per-series fill opacity. |
showDots | boolean | true | Vertex markers. |
size | "sm" | "md" | "lg" | "md" | Square size cap. |
radialLines | boolean | true | Draw the spokes running out to each axis. |
showRadiusAxis | boolean | false | Show the radial value scale. |
maxValue | number | data max | Pin the radial scale to [0, maxValue]. |
categoryFormatter | (value: string) => string | — | Format the axis (spoke) labels. |
valueFormatter | (value, index) => string | — | Format radial tick text, e.g. "70%". |
showGrid / showLegend / showTooltip | boolean | — | Toggle chart chrome. |
label | string | — | Accessible name — renders role="figure". State the insight. |
description | string | — | Visually hidden summary read after the label. |
children | ReactNode | — | Recharts children rendered inside the chart. |
loading | boolean | false | Render the skeleton. |
error | ReactNode | — | Render an error message instead of the chart. |
emptyState | ReactNode | "No data to display" | Shown when data is empty. |
When to use a radar chart
A radar is good at shape — a profile you can recognise and compare at a glance. It is poor at magnitude: readers judge radial length less accurately than aligned length, and a filled area grows with the square of its values, so a series twice as high looks four times as big. If the question is "which is larger, and by how much", a grouped bar chart answers it better.
- Three axes minimum, eight is a comfortable maximum. Below three the polygon degenerates; the largest we found in the wild is a product that caps itself at twelve.
- Axis order changes the picture. The same numbers make a different shape when reordered, so choose an order that means something (a workflow, a ranking, a fixed template) and keep it stable across charts.
- Three series is the practical ceiling. Past that, filled areas hide one
another — use
fillOpacity={0}for outlines, or split into small multiples. - Give every chart a
label. It names the figure to assistive tech;descriptioncarries the numbers a sighted reader gets from the shape. - All axes share one scale. If your metrics are not comparable, normalise them
before plotting (and say so in the
description) or use another chart.
Keyboard and assistive tech
Recharts' accessibilityLayer is on: the chart takes focus and the arrow keys
step between points, announcing each through the tooltip's polite live region.
VoiceOver users need QuickNav off for the arrow keys to reach the chart.