Radial Chart
- Stable
- WCAG 2.2 evidence
- RTL
A radial chart: concentric rings that each fill clockwise from 12 o'clock to show how far a measure has
got, with a column of values beside them. Data driven: rings in, SVG out; the same props render the
static SVG on the server. Accessibility: the figure is named by title, described by summary and backed by a data table with every value, maximum and
progress (WCAG 1.1.1, 1.3.1). The plot is ONE tab stop: any arrow key moves between rings (Right and Down go inwards), Home and End
jump to the first and last, and each ring is announced with its name, value, maximum and progress. Hovering or focusing a ring outlines
it and shows a tooltip that stays while hovered and closes with Escape (1.4.13, 2.1.1). Rings differ by colour AND fill pattern, and
the value rows repeat the pattern next to the name and value as text (1.4.1). The draw-in is behind motion-safe:. The plot is a
fixed 176 px and the value rows wrap under it on a narrow card (1.4.10); the plot stays left-to-right in right-to-left pages while
the card, rows and table mirror, and digits follow the locale. Consumer duties: a title that asks the question, a summary with the main message, units in valueFormat or formatValue (a ring of
87 reads "87" unless you format it as a percent), meaningful ring names.
import { RadialChart } from "@nexera-ui/react";- 11
- examples
- 20
- props
- 3
- live controls
- 1
- platform
- 9
- WCAG criteria
- 0
- blocks use it
Try every prop. Copy the code.
Change the props and the code updates. Check light and dark, LTR and RTL, and phone width.
import { RadialChart } from "@nexera-ui/react";
<RadialChart />Examples 10
The same examples as Storybook, rendered live. Open Code to copy one.
Own maximum
Each ring can have its own full turn: 112 of 128 here, 9 of 12 hours there. The rows show the value, the table the maximum and the progress.
import { RadialChart } from "@nexera-ui/react";
<RadialChart
title="Goals this quarter"
description="Progress against target"
summary="Hiring is at 112 of 128, onboarding at 9 of 12 and training at 41 of 60 hours."
rings={[
{ name: "Hires", value: 112, max: 128 },
{ name: "Onboarded", value: 9, max: 12 },
{ name: "Training hours", value: 41, max: 60 },
]}
/>;
Full empty and missing
A ring that is full, one at 0, and one with no value ("No data").
import { RadialChart } from "@nexera-ui/react";
<RadialChart
rings={[
{ name: "Complete", value: 100 },
{ name: "Not started", value: 0 },
{ name: "Not reported", value: null },
]}
/>;
Plot only
Without the value rows the rings stay named in the tooltip and the table.
import { RadialChart } from "@nexera-ui/react";
const percent = (value: number): string => `${String(value)}%`;
<RadialChart isLegendHidden formatValue={percent} />;
Loading
Loading, empty and error are ChartStates inside the card; the header stays.
import { RadialChart } from "@nexera-ui/react";
<RadialChart state={{ status: "loading" }} />;
Empty
import { RadialChart } from "@nexera-ui/react";
<RadialChart
rings={[]}
state={{
status: "empty",
title: "Nobody has checked in",
description: "Come back after nine.",
}}
/>;
Error state
import { Button, RadialChart } from "@nexera-ui/react";
<RadialChart
state={{
status: "error",
title: "Couldn't load attendance",
description: "Check your connection and try again.",
action: (
<Button variant="secondary" size="sm">
Try again
</Button>
),
}}
/>;
Single ring
One ring.
import { RadialChart } from "@nexera-ui/react";
const percent = (value: number): string => `${String(value)}%`;
<RadialChart
title="Profile completion"
summary="86 percent of profiles are complete."
rings={[{ name: "Complete profiles", value: 86 }]}
/>;
Many rings
Eight rings get thinner to fit; the rows and the table carry every value. Use a bar chart past about five.
import { RadialChart } from "@nexera-ui/react";
const percent = (value: number): string => `${String(value)}%`;
<RadialChart
title="Department attendance"
summary="Attendance is between 62 and 96 percent in every department."
rings={[
"Engineering",
"Sales",
"Support",
"Operations",
"Finance",
"People",
"Marketing",
"Legal",
].map((name, i) => ({ name, value: 96 - i * 5 }))}
/>;
Long labels
Long ring names wrap in the rows, the tooltip and the table; nothing is cut off.
import { RadialChart } from "@nexera-ui/react";
<RadialChart
rings={[
{
name: "Employees who checked in through the mobile application from a remote location",
value: 38,
},
{ name: "On time", value: 74 },
{ name: "Late with an approved reason from their manager", value: 9 },
]}
/>;
Right to left
Right-to-left page: the card, value rows and table mirror, the rings keep their clockwise direction from 12 o'clock, digits follow the locale.
import { RadialChart } from "@nexera-ui/react";
const percent = (value: number): string => `${String(value)}%`;
<RadialChart
title="حضور اليوم"
description="سجّل ١١٢ من ١٢٨ حضورهم"
summary="سجّل ٨٧ بالمئة من الموظفين حضورهم اليوم، وكان ٧٤ بالمئة منهم في الوقت المحدد."
valueFormat={{ style: "percent" }}
rings={[
{ name: "حاضر", value: 0.87, max: 1 },
{ name: "في الوقت", value: 0.74, max: 1 },
{ name: "عن بُعد", value: 0.18, max: 1 },
]}
valueLabel="القيمة"
maxLabel="الحد الأقصى"
progressLabel="التقدم"
table={{ categoryLabel: "الفئة", label: "عرض البيانات كجدول" }}
/>;
Props 20
Press "Try it" on a card to load that prop into the playground.
20 props shown
rings*Nexerareadonly RadialRing[]The rings from the outermost to the innermost. Each ring is one measure against its own full turn: attendance, goals, completion.
Rings differ by colour AND fill pattern (WCAG 1.4.1), and each ring's value, name, maximum and progress are in the value rows, the
tooltip and the table. Keep to about five rings; use a BarChart to compare many values precisely.
maxNexeranumberThe value of a full turn for every ring that does not set its own max.
valueLabelNexerastringHeader of the value column of the table and label of the value row in the tooltip and the point labels. Translate it.
maxLabelNexerastringHeader of the maximum column and label of the maximum row in the tooltip and the point labels. Translate it.
progressLabelNexerastringHeader of the progress column (the value as a share of the maximum) and label of the progress row in the tooltip and the point labels. Translate it.
title*NexerastringChart title: the question the chart answers ("Headcount"). Required: it names the figure, the plot and the data table (WCAG 1.1.1).
descriptionNexeraReactNodeSecondary line under the title.
summary*NexerastringOne or two sentences with what the chart shows and its main message ("Headcount grew from 96 to 128 over twelve months"). Required: it describes the figure for assistive technology (WCAG 1.1.1); the data table has the details.
rangeNexeraChartRangePropsRange switch in the header: 3M, 6M, 12M. Pass the data of the chosen range back in.
actionsNexeraReactNodeExtra controls in the header, after the range switch.
kpiNexeraChartKpiPropsKPI row above the plot: the headline number with an optional label or change badge.
stateNexeraChartPlotStateThe state that replaces the plot: loading (skeleton), empty or error. A chart without a value to plot shows the empty state
by itself; pass { status: "empty", title, description, action } to give it your wording.
tableNexeraChartTableOptionsThe data table that is the text alternative: caption, first-column header and whether it is visible or only for assistive technology.
localeNexerastringLocale of digits and separators.
valueFormatNexeraNumberFormatOptionsIntl.NumberFormat options for values ({ style: "currency", currency: "PKR" }, { style: "percent" }). Serialisable, so it
works from a Server Component; formatValue needs a Client Component.
formatValueNexera(value: number) => stringFormats a value for tooltips, point labels and the table (full precision). Wins over valueFormat.
heightNexeranumberHeight of the plot in px. The card grows with the header, KPI and legend around it.
isLegendHiddenNexerabooleanHide the legend. The series stay named in the tooltip, the point labels and the table.
plotLabelNexerastringAccessible name of the plot group. Translate it.
noDataLabelNexerastringText for a missing value in the tooltip and the table. Translate it.
* Required. React Aria props shown are the ones most apps use; the component accepts the rest of its React Aria props too.
Accessible by default.
Built on React Aria, and covered by the WCAG 2.2 evidence generated on every build.
WCAG 2.2 evidence
8 direct · 1 supporting- 1.1.1Non-text ContentLevel A · tested directly
- 1.3.1Info and RelationshipsLevel A · tested directly
- 1.4.1Use of ColorLevel A · tested directly
- 1.4.10ReflowLevel AA · tested directly
- 1.4.13Content on Hover or FocusLevel AA · tested directly
- 2.1.1KeyboardLevel A · tested directly
- 2.3.3Animation from InteractionsLevel AAA · supporting test
- 4.1.2Name, Role, ValueLevel A · tested directly
- 4.1.3Status MessagesLevel AA · tested directly
Fits any width.
Nexera components respond to the space they are given. Drag the corner of the frame, or pick a width.
Styling hooks
Pass className to add Tailwind classes (merged last). State is exposed as data attributes, so you can style it with variants like data-pressed:.
<RadialChart className="shadow-sm" />Used in blocks
No block uses RadialChart yet.
Related components
- AccordionItemOne collapsible section of an FAQ or settings page.
- AreaChartA stacked area chart: layers piled up over time with the title, range switch, actions and legend of the card.
- AvailabilityRowOne person's availability across a window of the day: an avatar with initials and the name, and a timeline with the busy periods drawn as bars.
- AvatarA person or organisation shown as a photo, initials or an icon.
- AvatarGroupA row of overlapping avatars with a "+N" chip for the rest.
- AvatarLabelAn avatar with a name and an optional subtitle beside it, for a person in a list, a header or a mention.
- BadgeShort status or category label: `Tone` x `Variant` x `Size`, with an optional dot, leading icon and remove button.
- BadgeGroupA pill that pairs a small badge with a short message, for an announcement or a status line.