Charts
Pie Chart
A parts-of-a-whole comparison paired with a persistent legend and exact-value table.
When to use it: Use it for a few non-negative categories whose total forms a meaningful whole.
Example
import {
ChartDescription,
ChartLegend,
ChartTable,
ChartTitle,
ChartTooltip,
PieChart,
PieChartPlot,
PieChartSlice,
} from "@comp0/react";
const traffic = [
{ label: "Direct", value: 4200 },
{ label: "Search", value: 3100 },
{ label: "Referrals", value: 1800 },
{ label: "Social", value: 900 },
] as const;
const colors = [
"bg-teal-600 fill-teal-600 dark:bg-teal-400 dark:fill-teal-400",
"bg-sky-600 fill-sky-600 dark:bg-sky-400 dark:fill-sky-400",
"bg-violet-600 fill-violet-600 dark:bg-violet-400 dark:fill-violet-400",
"bg-amber-500 fill-amber-500 dark:bg-amber-400 dark:fill-amber-400",
] as const;
const formatVisits = (value: number) => value.toLocaleString("en-US");
export function Example() {
return (
<PieChart
values={traffic}
categoryLabel="Source"
valueLabel="Visits"
formatValue={formatVisits}
className="w-full max-w-2xl rounded-lg has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-4 has-[:focus-visible]:outline-teal-600 dark:has-[:focus-visible]:outline-teal-400"
>
<ChartTitle className="text-base font-semibold text-zinc-950 dark:text-zinc-50">
Traffic by source
</ChartTitle>
<PieChartPlot
aria-label="Pie chart showing traffic share by source"
className="mx-auto mt-5 aspect-square w-full max-w-xs overflow-visible"
>
{(slice) => (
<PieChartSlice slice={slice} className="group outline-none">
<path d={slice.path} className={colors[slice.index]} />
</PieChartSlice>
)}
</PieChartPlot>
<ChartLegend className="mt-5 grid gap-2 text-sm [&>li]:grid [&>li]:grid-cols-[auto_1fr_auto] [&>li]:items-center [&>li]:gap-2">
{({ formattedValue, index, percentage, value }) => (
<>
<span aria-hidden="true" className={`size-3 rounded-sm ${colors[index]}`} />
<span>{value.label}</span>
<span>
{formattedValue} · {percentage.toFixed(0)}%
</span>
</>
)}
</ChartLegend>
<ChartDescription className="mt-5 text-sm text-zinc-600 dark:text-zinc-400">
Direct visits are the largest source, while social accounts for less than one tenth.
</ChartDescription>
<ChartTable className="mt-4 w-full border-collapse text-left text-sm [&_td]:border-t [&_td]:border-zinc-200 [&_td]:py-2 [&_th]:border-zinc-200 [&_th]:py-2 dark:[&_td]:border-zinc-800 dark:[&_th]:border-zinc-800">
<caption className="sr-only">Traffic source values</caption>
<thead>
<tr>
<th scope="col">Source</th>
<th scope="col">Visits</th>
</tr>
</thead>
<tbody>
{traffic.map((source) => (
<tr key={source.label}>
<th scope="row">{source.label}</th>
<td>{formatVisits(source.value)}</td>
</tr>
))}
</tbody>
</ChartTable>
<ChartTooltip className="pointer-events-none z-50 rounded-md bg-zinc-950 px-2 py-1 text-sm text-white shadow-lg dark:bg-zinc-50 dark:text-zinc-950" />
</PieChart>
);
}Anatomy
Dashed frames are invisible state providers; shaded shapes own real DOM. Numbered pins match the list below.
A wireframe sketch of the assembled component. Each numbered marker matches a part in the list that follows.
PieChart
Native figure sharing non-negative categories, labels, and formatting with every part. Owns a DOM element.
ChartTitle
Native figcaption that visibly names the figure. Owns a DOM element.
PieChartPlot / PieChartSlice
SVG slices showing each category as a proportion of the whole. Owns a DOM element.
ChartLegend
Persistent native list pairing every category with its formatted value and percentage. Owns a DOM element.
ChartDescription
Visible prose summarizing the important proportion. Owns a DOM element.
ChartTable
Native table composed with an explicit caption, headers, and rows from the chart values. Owns a DOM element.
ChartTooltipOptional
Optional floating value label shown on hover or focus. Owns a DOM element.
Step by step
- 1
Add the main part
Start PieChart with labelled values plus headings for categories and values.
- 2
Add the supporting parts
Add ChartTitle, PieChartPlot, and ChartLegend; wrap each rendered path in PieChartSlice so one slice is tabbable and arrow keys reveal the rest.
- 3
Make the behavior clear
Add ChartDescription and ChartTable for persistent context and exact values; optionally add ChartTooltip for pointer and keyboard details.
Exampletsx <PieChart values={traffic} categoryLabel="Source" valueLabel="Share"> <ChartTitle>Traffic by source</ChartTitle> <PieChartPlot aria-label="Pie chart showing traffic share by source"> {(slice) => ( <PieChartSlice slice={slice}> <path d={slice.path} /> </PieChartSlice> )} </PieChartPlot> <ChartLegend /> <ChartDescription>Direct visits are the largest source.</ChartDescription> <ChartTable> <caption>Traffic source values</caption> <thead> <tr> <th scope="col">Source</th> <th scope="col">Share</th> </tr> </thead> <tbody> {traffic.map((source) => ( <tr key={source.label}> <th scope="row">{source.label}</th> <td>{source.value}%</td> </tr> ))} </tbody> </ChartTable> <ChartTooltip /> </PieChart>;
Keyboard
- ⇥
- Enters the chart at its current slice and leaves with one more Tab.
- →↓
- Moves to the next slice, wrapping from the last slice to the first.
- ←↑
- Moves to the previous slice, wrapping from the first slice to the last.
- Home
- Moves to the first slice.
- End
- Moves to the last slice.
- Esc
- Dismisses an open ChartTooltip.
Forms and accessibility
Charts are descriptive content and do not create form values.
Accessibility checklist
- Give PieChartPlot a concise aria-label that identifies the chart type and whole being divided.
- Keep ChartLegend persistent and adjacent to the graphic so labels and values never depend on hover or focus.
- Wrap custom paths in PieChartSlice to expose one roving tab stop; each slice receives its category, formatted value, and percentage as a name.
- ChartTooltip is an optional visual enhancement for hover and focus; never make it the only source of a value.
- Include ChartTable when exact values matter; its native headers preserve relationships during screen-reader navigation.
- Do not use color as the only way to distinguish slices; pair every swatch with its visible category label.
API reference
import { ChartDescription, ChartLegend, ChartTable, ChartTitle, ChartTooltip, PieChart, PieChartPlot, PieChartSlice } from "@comp0/react";PieChart
DOM elementNative figure sharing non-negative categories, labels, and formatting with every part.
| Prop | Type | Description |
|---|---|---|
values | readonly CategoricalChartValue[] | Non-negative category values whose total must be positive. |
categoryLabel | string | Visible headings for legend and table values. |
valueLabel | string | Visible headings for legend and table values. |
formatValue | (value: number) => string | Formatter shared by legend and table values. |
ChartTitle
DOM elementNative figcaption that visibly names the figure.
PieChartPlot / PieChartSlice
DOM elementSVG slices showing each category as a proportion of the whole.
| Prop | Type | Description |
|---|---|---|
aria-label | string | Concise text alternative naming the graphic and whole. |
children | (slice: PieChartSliceState) => ReactNode | Custom slice renderer receiving the category, path, angles, and percentage. |
slice | PieChartSliceState | Slice state passed from the plot to PieChartSlice. |
PieChartSlice children | ReactNode | SVG shapes grouped into one named, keyboard-reachable slice. |
ChartLegend
DOM elementPersistent native list pairing every category with its formatted value and percentage.
| Prop | Type | Description |
|---|---|---|
children | (item: ChartLegendItem) => ReactNode | Custom legend entry renderer receiving category, formatted value, and percentage. |
ChartDescription
DOM elementVisible prose summarizing the important proportion.
ChartTable
DOM elementNative table composed with an explicit caption, headers, and rows from the chart values.
| Prop | Type | Description |
|---|---|---|
children | ReactNode | Native caption, thead, tbody, and optional tfoot markup. |
ChartTooltip
OptionalDOM elementOptional floating value label shown on hover or focus.
| Prop | Type | Description |
|---|---|---|
placement | PopoverPlacement | Side of the active mark; defaults to "top". |
offset | number | Distance from the active mark; defaults to eight pixels. |
children | ReactNode | (details: ChartValueDetails) => ReactNode | Custom content receiving the active slice's formatted details. |
Style hooks
Attributes that appear while a state is true. Target them with Tailwind data variants such as data-open:bg-zinc-100, or with any CSS selector.
PieChartPlot / PieChartSlice
| Style hook | Meaning |
|---|---|
[data-value] | The numeric value represented by this slice or legend entry. |
[data-active] | The slice currently reached by pointer or keyboard. |
ChartLegend
| Style hook | Meaning |
|---|---|
[data-value] | The numeric value represented by this slice or legend entry. |
ChartTooltip
| Style hook | Meaning |
|---|---|
[data-open] | A value tooltip is visible. |