const data = /* ... */;
<RadarChart
data={data}
x="month"
y={["desktop", "mobile"]}
labels={{ desktop: "Desktop", mobile: "Mobile" }}
points
ariaLabel="Desktop and mobile visitors, January through June"
/>A radar chart wraps a category axis into a circle and draws each series as a closed shape. Reach for it when the reader should judge a profile — the balance across five to eight categories — and compare two of them at a glance. It is a poor tool for reading exact values or for ranking: the eye compares areas, and area grows with the square of the value. When precision matters, use the bar chart.
RadarChart takes your rows and the names of the fields to read. Colors, typography, grid, tooltip, and legend all come from your design system.
Installation
pnpm dlx shadcn@latest add @dotui/chart-radarIt brings the chart core — the host, the palette, and the shared frame — along with it.
Usage
import { RadarChart } from "@/components/ui/chart-radar"Point x at the category field and y at the value field — one row per category. A chart is a figure, so ariaLabel is required.
const data = [
{ month: "Jan", desktop: 186 },
{ month: "Feb", desktop: 305 },
{ month: "Mar", desktop: 237 },
]
export function Example() {
return (
<RadarChart
data={data}
x="month"
y="desktop"
labels={{ desktop: "Desktop" }}
legend={false}
ariaLabel="Desktop visitors by month"
/>
)
}data is compared by identity, so define it outside your component — or memoize it. Every other prop is a flat scalar and can change freely.
Data shape
Two shapes work, and both draw the same chart.
Wide rows carry one column per series. Pass the columns as an array, and name them with labels:
<RadarChart
data={data}
x="month"
y={["desktop", "mobile"]}
labels={{ desktop: "Desktop", mobile: "Mobile" }}
ariaLabel="Visitors by device"
/>Long rows carry one row per series per category, with the series key in its own field. Pass that field as series, and set seriesOrder to lead the color slots and the legend — series the data carries but seriesOrder omits follow in the order they first appear:
<RadarChart
data={data}
x="month"
y="visitors"
series="device"
seriesOrder={["desktop", "mobile"]}
labels={{ desktop: "Desktop", mobile: "Mobile" }}
ariaLabel="Visitors by device"
/>Series take colors from --chart-1 through --chart-8 in order. Every series is drawn on one shared radius scale running from zero to the largest value — a radar with two units on it is a chart with two meanings, so normalize first.
Grid and labels
The rings, the spokes, and the circumference labels are three separate switches: grid draws the rings, spokes the lines running out to each category, and axes the labels. gridShape makes the rings circles instead of polygons, gridTicks sets how many there are, and gridFill tints the area inside the outer one.
<RadarChart
data={data}
x="month"
y="desktop"
gridShape="circle"
gridFill={0.2}
spokes={false}
ariaLabel="Desktop visitors by month"
/>formatX rewrites the circumference labels, and axisDetail adds a second, muted line above each one — the value at that spoke, a share, a delta.
Annotations
polarMarks accepts raw TanStack Charts polar mark layers, spliced into the polar container on the same angle and radius scales. Cartesian marks would land outside the circle, so this is the escape hatch a radar uses. children renders as HTML above the chart, ignoring pointer events.
Animation
The polygon grows from the center on first client paint and springs between data states — server-rendered charts replay the entrance once hydration lands. animate takes false for an immediate repaint, or a tween or spring configuration to change the feel.
Accessibility
ariaLabelis required and names the figure; addariaDescriptionwhen the takeaway needs a sentence.- The chart surface is in the tab order. Arrow keys move between spokes,
HomeandEndjump to the first and last,Enterand Space pin the tooltip, andEscapedismisses it. - Hovering or focusing a spoke reports every series at that category, named and valued — color alone never carries the distinction.
tooltip={false}keeps the focus stops but removes their live region, so screen readers land on silent points. Turn it off only for a decorative chart.
Examples
Default
Dots
Multiple Series
Legend
Icon Legend
Lines Only
Detailed Labels
Circular Grid
Circular Grid, No Spokes
Circular Grid, Filled
Filled Grid
Custom Grid
No Grid
Radius
API Reference
RadarChart
Radar chart. Give it one row per category plus the fields to read: one `y` field per series for wide rows, or a single `y` with `series` for long rows.
| Prop | Type | Default | |
|---|---|---|---|
readonly unknown[] | — | ||
string | — | ||
readonly string[] | string | — | ||
string | — | ||
readonly string[] | — | ||
Readonly<Record<string, string>> | — | ||
string | — | ||
number | 0.6 | ||
number | 2.25 | ||
boolean | false | ||
number | 0.78 | ||
number | — | ||
"circle" | "polygon" | "polygon" | ||
number | 4 | ||
number | — | ||
string | "var(--chart-1)" | ||
boolean | matches `grid` | ||
ChartFormat | — | ||
boolean | true | ||
boolean | true | ||
boolean | true | ||
ChartFormat | — | ||
ChartFormat | — | ||
readonly PolarMarkLayer[] | — | ||
union | groups by angular ray | ||
string | — | ||
string | — | ||
number | — | ||
'point' | 'pointer' | 'group-center' | "group-center" | ||
boolean | true | ||
boolean | true | ||
ChartAnimate | { type: "spring", stiffness: 170, damping: 26 } | ||
number | 256 | ||
number | — | ||
number | — | ||
number | — | ||
string | — | ||
ReactNode | function | — | ||
ReactNode | — | ||
Last updated on 9/7/2026