Data Storytelling

Radar Skill Chart

Multi-axis radar chart: a drawing animation on reveal, values on vertex hover, and two series overlaid for comparison.

A multi-axis radar chart drawn in pure SVG: on reveal the data polygon expands out from the center while the outline grows along its edges, hovering a vertex floats a tooltip with that axis's value, and a second data series can be overlaid semi-transparently for comparison — which suits skill assessments, product comparisons, and similar storytelling.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add radar-skill-chart.en

New project or installation trouble? Start here →

Radar Skill Chart is a React radar chart (spider chart) component built with SVG and Motion (motion/react), with no charting library involved. On entry the data polygon expands from the center while its outline draws along the edges; you can overlay a second series for comparison, and hovering or focusing a vertex shows a tooltip.

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

300
100
4
Active edited usageDefault props
<RadarSkillChart/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

When to use

  • Skill breakdowns on a portfolio or resume page
  • Comparing a candidate's strengths with a role's requirements in a hiring dashboard
  • Product comparison pages showing two plans across several dimensions
  • Course or assessment results showing scores per subject
  • Not a good fit: with only one or two dimensions, or when you need a trend over time, a radar chart is harder to read; use the bar or line charts in Animated Charts

Installation

npx webberui@latest add https://webberui.com/r/radar-skill-chart.en.json

Or, once registries are configured in components.json, install it as @webberui/radar-skill-chart.

Usage

import { RadarSkillChart } from "@/components/ui/radar-skill-chart";

<RadarSkillChart
  axes={[
    { label: "React", value: 92 },
    { label: "TypeScript", value: 85 },
    { label: "Testing", value: 62 },
    { label: "Collaboration", value: 88 },
  ]}
  series2={[60, 48, 30, 72]}
  seriesLabel="This year"
  series2Label="Last year"
/>

Examples

A 1 to 5 rating

Set max to 5 and rings to 5 so each ring matches one point.

import { RadarSkillChart } from "@/components/ui/radar-skill-chart";

<RadarSkillChart
  max={5}
  rings={5}
  axes={[
    { label: "Ease of use", value: 4 },
    { label: "Performance", value: 3 },
    { label: "Docs", value: 5 },
    { label: "Support", value: 4 },
    { label: "Pricing", value: 2 },
  ]}
/>;

Candidate versus role requirements

Put the candidate's scores in the main series and the role's thresholds in series2, in the same order as axes; switch to brand colors with color and color2.

<RadarSkillChart
  axes={[
    { label: "Frontend", value: 88 },
    { label: "Backend", value: 54 },
    { label: "System design", value: 70 },
    { label: "Communication", value: 82 },
    { label: "Leadership", value: 45 },
    { label: "Product sense", value: 76 },
  ]}
  series2={[80, 60, 65, 70, 50, 60]}
  seriesLabel="Candidate"
  series2Label="Role"
  color="#0ea5e9"
  color2="#a3a3a3"
  size={360}
/>

Load data from an API

It is a client component, so fetch in a Server Component and pass the data straight in as props.

// app/team/[id]/page.tsx
import { RadarSkillChart } from "@/components/ui/radar-skill-chart";

export default async function MemberPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const res = await fetch(`https://api.example.com/members/${id}/skills`);
  const skills: { name: string; score: number }[] = await res.json();

  return (
    <RadarSkillChart
      axes={skills.map((s) => ({ label: s.name, value: s.score }))}
    />
  );
}

Props

PropTypeDefaultDescription
axesRadarAxisDatum[]—Per-axis data { label, value }, at least 3 axes, with the primary series values embedded in it; long labels wrap at spaces, and if labels still reach past the chart the whole chart scales down so they stay within size
series2number[]—The second set of values (overlaid semi-transparently for comparison); the order must line up with axes
seriesLabelstring"Series A"Name of the primary series, used in the legend and the tooltip when there are two series (the English source includes this default, so pass this prop to localize it)
series2Labelstring"Series B"Name of the second series (the English source includes this default, so pass this prop to localize it)
maxnumber100Value ceiling, corresponding to the outermost ring
sizenumber320Rendered size (px, width = height)
ringsnumber4Number of concentric grid rings
colorstring"#6366f1"Primary series color (any valid CSS color value)
color2string"#f59e0b"Second series color
classNamestring—Forwarded to the outermost container

Accessibility

  • The SVG has a descriptive aria-label that announces every axis label and every series value in full
  • Each vertex hit area is keyboard focusable (Tab moves through them in order) and shows the tooltip on focus just as on hover, each carrying its own aria-label announcing the value
  • When the user has "reduce motion" enabled at the system level, the expansion and stroke-drawing animations are skipped and the finished shape is shown directly
  • Hovering also darkens the matching axis line and label, giving an extra visual cue for lining things up

FAQ

Does it work with the Next.js App Router?

Yes. The file already starts with "use client", so import it from a Server Component page and pass data in as props.

Why is the chart not showing?

A radar chart needs at least 3 axes to have an area, so the component returns null when axes has fewer than 3 items. Values above max or below 0 are clamped to the 0 to max range.

Does the animation replay when data changes?

No. The entrance expansion runs once on mount; after that, changes to axes or series2 recompute the polygon coordinates directly to the new values.

How do I change colors and size?

color and color2 take any CSS color value for the two series; size sets the rendered width and height in px, scaling the internal viewBox (which grows when axis labels are long, so the chart shrinks to keep them inside); rings sets the number of grid rings.

Can screen readers read the values?

Yes. The SVG aria-label lists every dimension's values, and each vertex hit area can be focused with Tab and announces that axis. With the OS "reduce motion" setting on, the entrance animation is skipped.