A radar chart with filled or outlined shapes, polygon or circular rings, dots and a fixed scale, in a frame with a legend that switches series off, a table of every axis, patterns and states.
Team skills
Self-rated, 0 to 100
"use client"
import { ChartRadar } from "@/components/ballmac/chart-radar"
const data = [
{ skill: "Design", level: 86 },
{ skill: "Frontend", level: 94 },
{ skill: "Backend", level: 72 },
{ skill: "Data", level: 61 },
{ skill: "Writing", level: 78 },
{ skill: "Research", level: 83 },
]
export default function ChartRadarDemo() {
return <ChartRadar className="w-full max-w-md" title="Team skills" description="Self-rated, 0 to 100" data={data} xKey="skill" categoryLabel="Skill" series={[{ key: "level", label: "Level" }]} domain={[0, 100]} />
}Installation
$ pnpm dlx shadcn@latest add @ballmac/chart-radarInstall the dependencies.
$ pnpm add recharts@^3Add the Ballmac items it builds on.
$ pnpm dlx shadcn@latest add @ballmac/chart @ballmac/chart-frameCopy the source into your project.
components/ballmac/chart-radar.tsx// Ballmac UI: Chart Radar. https://ui.ballmac.com/components/chart-radar "use client" import * as React from "react" import { PolarAngleAxis, PolarGrid, PolarRadiusAxis, Radar, RadarChart } from "recharts" import { ChartTooltip, ChartTooltipContent } from "@/components/ballmac/chart" import { ChartFrame, type ChartFrameProps } from "@/components/ballmac/chart-frame" type ChartRadarProps = Omit<ChartFrameProps, "children" | "summaryMode" | "skeleton" | "table"> & { /** Fill each shape. Turn it off to draw outlines, which keeps overlapping series readable. */ fillShape?: boolean /** Draw a dot on every axis. */ dots?: boolean /** The shape of the grid rings. */ grid?: "polygon" | "circle" | "none" /** Fix the scale, for example `[0, 100]`. */ domain?: [number, number] /** Write the scale along one axis. */ scale?: boolean tooltip?: boolean } /** * A radar chart in a ChartFrame: filled or outlined shapes, polygon or circular rings, dots, a fixed scale, a legend that switches series off, * a table of every axis, patterns, and loading, empty and error states. */ function ChartRadar({ data, xKey, series, fillShape = true, dots = false, grid = "polygon", domain, scale = false, tooltip = true, aspect = "square", ...frame }: ChartRadarProps) { // Axes run clockwise from the top. Right to left they run the other way, which is the same ring with the first axis kept on top and the rest reversed. const points = (rtl: boolean) => (rtl && data.length > 2 ? [data[0]!, ...data.slice(1).reverse()] : data) return ( <ChartFrame data={data} xKey={xKey} series={series} summaryMode="ranking" skeleton="radar" aspect={aspect} {...frame}> {(chart) => ( <RadarChart data={points(chart.rtl)} outerRadius="72%" margin={{ top: 12, right: 28, bottom: 12, left: 28 }} accessibilityLayer> <defs>{chart.defs}</defs> {grid !== "none" ? <PolarGrid gridType={grid} /> : null} <PolarAngleAxis dataKey={xKey} tick={{ fill: "var(--muted-foreground)", fontSize: 12 }} tickFormatter={(v: unknown) => chart.formatCategory(v)} /> {scale ? <PolarRadiusAxis angle={chart.rtl ? 90 + 180 / Math.max(3, data.length) : 90 - 180 / Math.max(3, data.length)} domain={domain} tick={{ fill: "var(--muted-foreground)", fontSize: 10 }} axisLine={false} tickLine={false} /> : <PolarRadiusAxis domain={domain} tick={false} axisLine={false} tickLine={false} />} {tooltip ? <ChartTooltip cursor={false} content={<ChartTooltipContent indicator="line" />} /> : null} {series.map((s) => ( <Radar key={s.key} dataKey={s.key} hide={chart.isHidden(s.key)} stroke={chart.color(s.key)} strokeWidth={2} fill={chart.patterns ? chart.fill(s.key) : chart.color(s.key)} fillOpacity={fillShape || chart.patterns ? (chart.patterns ? 1 : 0.22) : 0} dot={dots ? { r: 3.5, fillOpacity: 1 } : false} isAnimationActive={chart.animate} animationDuration={700} /> ))} </RadarChart> )} </ChartFrame> ) } export { ChartRadar, type ChartRadarProps }Update the import paths to match your project setup.
Usage
import { ChartRadar } from "@/components/ballmac/chart-radar"The full example is in the Code tab above.
Examples
Two series with the scale
Before and after
Scorecard, with the scale shown
"use client"
import { ChartRadar } from "@/components/ballmac/chart-radar"
const data = [
{ metric: "Speed", before: 52, after: 81 },
{ metric: "Accuracy", before: 74, after: 88 },
{ metric: "Reliability", before: 63, after: 79 },
{ metric: "Cost", before: 70, after: 66 },
{ metric: "Support", before: 58, after: 84 },
{ metric: "Docs", before: 45, after: 77 },
]
export default function ChartRadarCompare() {
return (
<ChartRadar
className="w-full max-w-md"
title="Before and after"
description="Scorecard, with the scale shown"
data={data}
xKey="metric"
categoryLabel="Metric"
domain={[0, 100]}
scale
series={[
{ key: "before", label: "Before", color: "var(--chart-3)" },
{ key: "after", label: "After" },
]}
/>
)
}Circular rings, outlines and dots
Week at a glance
Minutes of focus and of calls, in tens
"use client"
import { ChartRadar } from "@/components/ballmac/chart-radar"
const data = [
{ day: "Mon", focus: 62, calls: 34 },
{ day: "Tue", focus: 71, calls: 28 },
{ day: "Wed", focus: 58, calls: 47 },
{ day: "Thu", focus: 76, calls: 22 },
{ day: "Fri", focus: 49, calls: 38 },
{ day: "Sat", focus: 18, calls: 6 },
{ day: "Sun", focus: 12, calls: 4 },
]
export default function ChartRadarCircle() {
return (
<ChartRadar
className="w-full max-w-md"
title="Week at a glance"
description="Minutes of focus and of calls, in tens"
data={data}
xKey="day"
categoryLabel="Day"
grid="circle"
fillShape={false}
dots
series={[
{ key: "focus", label: "Focus" },
{ key: "calls", label: "Calls", color: "var(--chart-4)" },
]}
/>
)
}Patterns for color blindness
Satisfaction by area
Three teams. Patterns tell the shapes apart without color
"use client"
import { ChartRadar } from "@/components/ballmac/chart-radar"
const data = [
{ area: "Onboarding", a: 82, b: 64, c: 48 },
{ area: "Billing", a: 71, b: 77, c: 52 },
{ area: "Search", a: 88, b: 59, c: 63 },
{ area: "Exports", a: 54, b: 72, c: 69 },
{ area: "Mobile", a: 66, b: 51, c: 78 },
]
export default function ChartRadarPatterns() {
return (
<ChartRadar
className="w-full max-w-md"
title="Satisfaction by area"
description="Three teams. Patterns tell the shapes apart without color"
data={data}
xKey="area"
categoryLabel="Area"
patterns="toggle"
domain={[0, 100]}
series={[
{ key: "a", label: "Team A" },
{ key: "b", label: "Team B" },
{ key: "c", label: "Team C" },
]}
/>
)
}API reference
| Prop | Type | Default |
|---|---|---|
fillShapeFill each shape. Turn it off to draw outlines, which keeps overlapping series readable. | boolean | true |
dotsDraw a dot on every axis. | boolean | false |
gridThe shape of the grid rings. | "polygon" | "circle" | "none" | "polygon" |
domainFix the scale, for example `[0, 100]`. | [number, number] | — |
scaleWrite the scale along one axis. | boolean | false |
tooltip | boolean | true |
Also accepts the standard attributes of its root element.
Accessibility
| Key | Action |
|---|---|
| Arrow keys | Move between axes (accessibilityLayer) |
| Tab, Enter / Space | Legend buttons switch series off and on; the Chart / Table switch shows the numbers as a table |
| Screen readers | The figure has a name and a summary written from the data |
Quality scorecard
Every applicable check passes
Measured in Chromium 153.0.8010.12 on October 19, 2026, on version 1.0.0, across 4 examples.
Passed: Accessibility scan
No serious or critical issues in light and dark, on a phone and a desktop (axe-core 4.13.0, WCAG 2.1 AA and best-practice rules).
Passed: Small screens
No sideways scrolling at 360, 390 or 1280 px wide.
Passed: Reduced motion
No endless animation to begin with.
Not applicable: Right to left
Not mirrored by design: Recharts draws its axes in SVG coordinates, which do not mirror.
Passed: Clean console
No console errors or React hydration errors while loading.
Passed: Keyboard
16 tab stops, each showing a visible change when it takes focus. 3 documented keys.
Not applicable: Loading, empty and error
Does not show data or take submissions.
Passed: Automated tests
Covered by charts-batch-families.test.tsx.
For information: Footprint
1.4 KB of source (gzip) in 1 file. Installs recharts.
Automated checks find a subset of problems and do not replace testing with a screen reader and a keyboard in your own app. What they miss.
Use with AI
<ChartRadar title description data xKey series={[{ key, label, color? }]} />. fillShape (false draws outlines), dots, grid: polygon | circle | none, domain ([0, 100]), scale (write the scale), legend, patterns, state and every ChartFrame prop. With the shadcn MCP server set up (guide), ask your agent:
Add the Ballmac UI Chart Radar (@ballmac/chart-radar) to this project with the shadcn MCP, then use it where it fits.
Use it for
- Comparing a few things across the same 5 to 8 dimensions
- Scorecards and profiles
Not for
- Precise comparison of values (use chart-bar)
- More than three or four overlapping series
Registry JSON: https://ui.ballmac.com/r/chart-radar.json
Credits
Free to use in personal and commercial projects.
- npm
- recharts@^3
Pairs well with
Chart Frame
The card around a Recharts chart: a Table view with CSV download, a legend that toggles series, patterns so series differ by more than color, a summary for screen readers, and loading, empty and error states.
Stat Card
A metric card with a clear comparison trend, optional visual, and readable value hierarchy.
Activity Feed
A compact actor and action feed with timestamps and an empty state.
Audio Player
A podcast-style audio player: play and pause, skip back and forward, a scrubber with chapter ticks, a named current chapter and a speed control. Works with a real file or as a silent demo.
Ballmac UI Pro
Need the whole screen, not one piece?
150 premium blocks (dashboards, settings, calendars, mail, shops, onboarding), a private shadcn registry and Figma tokens. Blocks that show data come with loading, empty and error states built in.
Not ready? Try three Pro blocks free
Leave your email and the blocks appear right here, ready to copy. Yours to use in any project.