Skip to content
ShineUI
Install

Radar chart

Several series over shared axes.

<sui-chart-radar>ChartsStable

About

Radar chart: a few series compared across the same axes (aging profile this year against last, a location scorecard). Rings and spokes are hairlines; series fill at 12% with a 2px outline and hollow vertex dots. Legend keys toggle series; hovering an axis lists every visible series value for that axis in the tooltip card.

Usage

<sui-chart-radar heading="Aged receivables profile" subtitle="Share of A/R by bucket, this year against last" max="60" caption="Closer to center on the late buckets means a healthier book"></sui-chart-radar> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); const el = document.querySelector("sui-chart-radar"); el.axes = ["Current", "1–30", "31–60", "61–90", "90+"]; /* string[] */ el.series = [{ label: "2026", data: [52, 25, 14, 6, 3] }, { label: "2025", data: [44, 27, 16, 8, 5], accent: true }]; /* SuiRadarSeries[] */ el.addEventListener("suiHover", (e) => console.log(e.detail)); </script>
import { SuiChartRadar } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { const axes = ["Current", "1–30", "31–60", "61–90", "90+"]; /* string[] */ const series = [{ label: "2026", data: [52, 25, 14, 6, 3] }, { label: "2025", data: [44, 27, 16, 8, 5], accent: true }]; /* SuiRadarSeries[] */ return <SuiChartRadar heading="Aged receivables profile" subtitle="Share of A/R by bucket, this year against last" max="60" caption="Closer to center on the late buckets means a healthier book" axes={axes} series={series} onSuiHover={(e) => console.log(e.detail)} />; }
<script setup> import { SuiChartRadar } from "@shineui/vue"; import "@shineui/core/shineui.css"; const axes = ["Current", "1–30", "31–60", "61–90", "90+"]; // string[] const series = [{ label: "2026", data: [52, 25, 14, 6, 3] }, { label: "2025", data: [44, 27, 16, 8, 5], accent: true }]; // SuiRadarSeries[] const onChange = (e) => console.log(e.detail); </script> <template> <SuiChartRadar heading="Aged receivables profile" subtitle="Share of A/R by bucket, this year against last" max="60" caption="Closer to center on the late buckets means a healthier book" :axes="axes" :series="series" @sui-hover="onChange" /> </template>
import { Component } from "@angular/core"; import { SuiChartRadar } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiChartRadar], template: `<sui-chart-radar heading="Aged receivables profile" subtitle="Share of A/R by bucket, this year against last" max="60" caption="Closer to center on the late buckets means a healthier book" [axes]="axes" [series]="series" (suiHover)="onChange($event)"></sui-chart-radar>`, }) export class Example { axes = ["Current", "1–30", "31–60", "61–90", "90+"]; // string[] series = [{ label: "2026", data: [52, 25, 14, 6, 3] }, { label: "2025", data: [44, 27, 16, 8, 5], accent: true }]; // SuiRadarSeries[] onChange(e: CustomEvent) { console.log(e.detail); } }

Basic

The plot is one tab stop named by an aria-label on the host, else by every visible series and every axis, and the arrow keys reach every point. Series take --viz-1 to --viz-4 in order and the one marked accent takes --accent. Legend keys are buttons with aria-pressed that toggle a series.

<sui-chart-radar heading="Aged receivables profile" subtitle="Share of A/R by bucket, this year against last" max="60" caption="Closer to center on the late buckets means a healthier book"></sui-chart-radar>
// setup(root): properties and events setup(root) { const el = root.querySelector('sui-chart-radar'); el.axes = AGING_AXES; el.series = AGING; }

Rings, diameter and scale

rings sets how many hairline rings the grid draws and diameter sets the square plot in px; leave max unset and the outer ring takes the largest data point. Hovering an axis lifts its spoke and lists every visible series in the tooltip.

<sui-chart-radar heading="Location scorecard" subtitle="Ops metrics indexed to 100 · six rings on a 260px plot · hover an axis" rings="6" diameter="260"></sui-chart-radar>
// setup(root): properties and events setup(root) { const el = root.querySelector('sui-chart-radar'); el.axes = SCORE_AXES; el.series = SCORE; }

Control in the head

The header-end slot puts a control beside the heading. The chart draws its head row whenever there is a heading or a filled header-end slot.

<sui-chart-radar heading="Aged receivables profile" subtitle="Share of A/R by bucket" max="60" diameter="190"> <sui-button slot="header-end" size="sm" variant="ghost" label="Export"></sui-button> </sui-chart-radar>
// setup(root): properties and events setup(root) { const el = root.querySelector('sui-chart-radar'); el.axes = AGING_AXES; el.series = AGING; }

Events

suiHover fires when the hovered axis changes and reports { index } into axes, with index -1 on leave. hoverIndex is mutable: a write from outside lifts that spoke and shows the tooltip without emitting, which is how sui-chart-synced mirrors a sibling.

Idle.
<div style="display:flex;flex-direction:column;gap:12px"> <sui-chart-radar id="rd-events" heading="Aged receivables profile" subtitle="Hover an axis" max="60"></sui-chart-radar> <span id="rd-log" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Idle.</span> </div>
// setup(root): properties and events setup(root) { const el = root.querySelector('#rd-events'); const log = root.querySelector('#rd-log'); el.axes = AGING_AXES; el.series = AGING; el.addEventListener('suiHover', (e) => { const i = e.detail.index; log.textContent = i < 0 ? 'Idle.' : `${AGING_AXES[i]}: ${AGING.map((s) => `${s.label} ${s.data[i]}%`).join(', ')}.`; }); }

Empty and loading

loading stands a shimmer in for the plot and marks the host aria-busy while the figures are on their way; the frame and its heading stay. A chart with nothing to plot keeps its frame and says so, and emptyMessage is the sentence. Both are the same on every chart.

<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px"> <sui-chart-radar heading="Location scorecard" subtitle="Fetching the scores" diameter="150" loading></sui-chart-radar> <sui-chart-radar heading="Location scorecard" subtitle="No scores yet" diameter="150" empty-message="No location has been scored."></sui-chart-radar> </div>

Keyboard

KeyAction
TabMoves to the plot, then through the legend keys, one button per series.
ArrowRight ArrowLeftMoves to the next axis clockwise, or back, and shows its tooltip; a reader hears the axis, the series and the figure.
ArrowDown ArrowUpMoves to the next or the previous series on the same axis.
Home EndMoves to the first or the last axis.
Ctrl+Home Ctrl+EndMoves to the first point of the first series or the last point of the last.
EscapeLeaves the point and hides the tooltip; the plot keeps focus.
Enter SpaceOn a legend key, hides its series and redraws the chart without it; press again to bring it back.

API

<sui-chart-radar>

Properties

PropertyAttributeTypeDefaultDescription
axesproperty onlystring[][]Axis labels, clockwise from the top. Property, not attribute.
captioncaptionstringstring | undefinedFooter note, separated by a hairline.
currencycurrencystring'USD'The ISO 4217 code money prints in: USD by default, EUR, JPY. The page's locale (setLocale(), else <html lang>) places the symbol and sets the grouping and the decimal mark, so EUR on a German page reads 121.695 €. An unknown code prints the bare figure. Ignored without money.
diameterdiameternumber210Diameter of the plot in px, axis labels included: a square this wide and tall. The labels sit outside the outer ring, anchored away from it by side, and wrap to three lines. A side label too wide to stand beside its point stands over or under it instead; only if that is not enough does the ring shrink, to 60% of its full radius at most, and whatever still does not fit ends in an ellipsis. The tooltip and the accessible name keep every label whole.
emptyMessageempty-messagestringstring | undefinedSentence drawn in place of the plot when there are no axes or no series. Defaults to "Nothing to plot yet."; the frame and its heading stay. The chart filters nothing, so this is its only empty string.
headingheadingstringstring | undefinedChart heading (the reference title prop, renamed because title is a native attribute). With what the chart shows, it names the plot for a reader; an aria-label or aria-labelledby on the host names the plot instead, and this summary then describes it.
hoverIndexhover-indexnumber-1Hovered axis, as its position in axes; -1 when idle. The pointer and the arrow keys set it, Right and Left turning clockwise and back, and the chart lifts that spoke and shows the tooltip. A write from outside does the same and emits nothing, which is how sui-chart-synced mirrors a sibling whose categories are these axes.
loadingloadingbooleanfalseThe figures are still on their way: a shimmer stands in for the plot, the frame and its heading stay, and the host is aria-busy. Reflected, so a page can style the wait.
maxmaxnumbernumber | undefinedOuter ring value. Defaults to the largest data point.
moneymoneybooleanfalseFormat the tooltip values as exact money in currency. Off, the figures print plainly, which is what a score or a share wants; set it for a balance. Reflected.
ringsringsnumber4Number of concentric rings.
seriesproperty onlySuiRadarSeries[][]Series to draw; each data array aligns with axes. Property, not attribute.
subtitlesubtitlestringstring | undefinedSecondary line under the heading.

Events

EventDetailReactVueDescription
suiHoverSuiChartHoverDetailonSuiHover@sui-hoverFires when the pointer or the keyboard moves the hover to another axis; { index: -1 } when the pointer leaves the plot, or Escape or a blur takes the keyboard's point away.

Methods

MethodSignatureDescription
setFocus()setFocus() => Promise<void>Moves focus to the plot, the chart's one tab stop, from which the arrow keys reach the points. Does nothing while there is nothing to plot.

Slots

SlotDescription
header-endControls at the end of the head row, after the heading.

Types

SuiChartHoverDetail interfaceDetail of suiHover: the hovered index, or -1 when the pointer leaves the plot. What the index counts is each chart's own: an x position here, a point on a scatter, an axis on a radar.
indexnumber
SuiRadarSeries interfaceOne polygon: data[i] pairs with axes[i]. accent paints it in the accent (one per chart); color overrides the ramp.
labelstring
datanumber[]
accent?boolean
color?stringThe polygon's own color for its outline, fill and points, which wins over accent; its legend and tooltip keys follow. Defaults to the ramp step for its place in series. Any CSS color or var(--token) works; use a token so the color follows the theme. A series past the fourth keeps its dotted outline.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also