Radar chart React component for Tailwind CSS v4
v1.0.0Added in 1.10.0
Compares one or more series across 3 to 12 categories on a spider web, for skills, product scores, survey results and plan comparisons.
Each series grows out of the center the first time the chart scrolls into view, the series a beat apart. Move the mouse around the web and the nearest spoke lights up, its label turns bold, the dots on it swell and a readout lists every visible series for that category. On a phone, tap near a spoke to pin the readout, drag around to move from spoke to spoke (a vertical drag still scrolls the page), tap the same spoke again or anywhere outside to clear it. With the keyboard, Tab to the chart: Right / Down move clockwise, Left / Up counter-clockwise (both wrap around), Home / End jump to the first and last axis and Escape clears; a live region reads each axis. Click or tap a legend entry to hide its series, hover or focus one to highlight it. Long category names wrap onto two lines, then shorten with an ellipsis on narrow screens; the full name stays in the readout and the screen reader table.
Radar chart is a Pro React 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 radar-chart, together with Chart kit, UIProvider 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 radar-chartPro item: the CLI needs a Pro account. See plans
Usage
Import RadarChart from @/components/ui/radar-chart.
import { RadarChart } from "@/components/ui/radar-chart"
const data = [
{ label: "Speed", starter: 6, pro: 9 },
{ label: "Storage", starter: 4, pro: 8 },
{ label: "Integrations", starter: 5, pro: 9 },
{ label: "Support", starter: 3, pro: 8 },
{ label: "Security", starter: 6, pro: 9 },
{ label: "Analytics", starter: 4, pro: 7 },
]
export function PlanComparison() {
return (
<RadarChart
data={data}
series={[
{ key: "starter", label: "Starter" },
{ key: "pro", label: "Pro" },
]}
max={10}
label="Starter vs. Pro, scored out of 10"
/>
)
}Props
data*ChartDatum[]One row per axis, in clockwise order from the top: { label: "Speed", starter: 6, pro: 9 }. A missing value sits at the center.
xKeydefault"label"stringThe row key that names each axis.
seriesRadarChartSeries[]Which keys to draw, in color order: { key, label?, color?, name?, dashed? }. dashed draws an outline only (a target or last period) with a muted tooltip row. Inferred from the numeric keys of the first rows when omitted.
colorsstring[]Per-position palette overrides: a slot ("series-3"), a tone ("neutral", "brand") or any CSS color.
sizedefault"md""sm" | "md" | "lg"The largest square the chart takes: 240, 320 or 420px. It shrinks to the container's width below that.
shapedefault"polygon""polygon" | "circle"The grid rings: straight-edged polygons or circles.
levelsdefault4numberHow many grid rings divide the range from min to max.
maxdefault0numberThe value on the outer ring (100 for percentages). 0, or anything not above min, rounds the largest visible value up so every ring lands on a round number.
mindefault0numberThe value at the center.
fillOpacitydefault0.18numberOpacity of each series' fill (dashed series have no fill).
strokeWidthdefault2numberOutline width in px.
curvedefault"linear""linear" | "cardinal"Straight edges between axes, or a smooth closed curve through every point.
showDotsdefaulttruebooleanA dot on every value. Without them the inspected axis still shows its dots.
showGriddefaulttruebooleanThe rings and spokes behind the series.
showAxisLabelsdefaulttruebooleanCategory names around the web. Off, the web takes the whole square.
showLevelLabelsdefaultfalsebooleanThe value of each ring, printed along the top spoke.
showLegenddefaulttruebooleanA legend for two or more series.
legendPositiondefault"bottom""top" | "bottom"Where the legend sits; it wraps onto more lines on narrow screens.
legendToggledefaulttruebooleanLegend entries are toggle buttons that hide and show a series (the last visible one stays).
showTooltipdefaulttruebooleanThe readout for the inspected axis. Off, the spoke still highlights and the live region still speaks.
tooltipModedefault"auto""auto" | "float" | "fixed"float beside the spoke's end (mouse), fixed above the chart where a finger cannot cover it (touch); auto picks by pointer.
valueFormatdefault"number"ChartValueFormatHow values print in the readout, ring labels and table: number, compact, currency or percent. Percent takes whole numbers (82 -> "82%").
currencydefault"USD"stringISO 4217 code for valueFormat currency.
decimalsdefault""numberFraction digits in the readout and table; auto by magnitude when unset (Default in the playground).
formatValue(value: number, context: { series: string; kind: "tooltip" | "level" | "table" }) => stringReplaces the built-in value formatting everywhere.
localedefault""stringLocale for numbers. Empty uses the UIProvider / LocaleProvider locale (en-US in the playground).
animateddefaulttruebooleanSeries grow out of the center on first view, morph when the data changes and fade when hidden or highlighted. Always off under reduced motion.
loadingdefaultfalsebooleanA round skeleton in the chart's square while data loads.
emptyStateReactNodeReplaces the built-in empty state when no row has a value.
labeldefault""stringThe accessible name of the chart and the table caption. Empty joins the series names.
descriptiondefault""stringA sentence for screen readers that states the takeaway.
surfacedefault""stringThe color behind the chart, used to ring the dots and halo the ring labels. Empty uses the surface token.
hiddenSeriesstring[]Hidden series keys when you control legend toggling; pair with onHiddenSeriesChange.
defaultHiddenSeriesstring[]Series hidden at first, uncontrolled.
onHiddenSeriesChange(keys: string[]) => voidCalled when a legend toggle hides or shows a series.
activeIndexnumber | nullThe inspected axis when you control it.
defaultActiveIndexdefault""number | nullThe axis inspected at first, uncontrolled. Unset (Default in the playground) starts with no readout.
onActiveIndexChange(index: number | null) => voidCalled as the inspected axis changes (mouse, touch or keyboard) and with null when it clears.
labelsPartial<RadarChartLabels>Strings: empty, loading, table, inspectHint, showSeries, hideSeries, axis (Category), compare ((comparison)).
classNamesPartial<Record<RadarChartSlot, string>>Classes for legend, plot, tooltip, table, grid, axisLabel, levelLabel, area, dot and inspect.
Parts
DEFAULT_RADAR_CHART_LABELS
constantThe English strings the chart uses, the chart kit's labels plus axis and compare. Spread it to translate a few.
RadarChartSeries
typeOne series: { key, label?, color?, name?, dashed? }.
RadarChartLabels
typeThe shape of the labels prop.
RadarChartFormatContext
typeWhat formatValue receives besides the value: the series key and where it prints ("tooltip", "level" or "table").
Examples
Skills against a bar
A neutral series over a dashed target, circle grid, cardinal curve and ring labels.
Single series
One brand-colored series on a circle grid; no legend for one series.
Three series, toggled legend
hiddenSeries controlled from the legend, which sits on top.
Percent
valueFormat percent with max 100; a long category name wraps onto two lines.
Loading and empty
The round skeleton and the empty state in the chart's square.
Dependencies
Files
- components/ui/radar-chart.tsx
FAQ
Is the React Radar chart component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Radar chart?
Run npx wingo-ui@latest add radar-chart. It also adds the Wingo UI items it builds on (Chart kit, UIProvider 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/radar-chart.
How does Radar chart behave on phones?
On a phone, tap near a spoke to pin the readout, drag around to move from spoke to spoke (a vertical drag still scrolls the page), tap the same spoke again or anywhere outside to clear it.