Chart
A Recharts wrapper that colors series from the chart tokens and adds a matching tooltip and legend.
@zenoxai/ui/components/chart Markdownfunction ChartDemo() {
const data = [
{ day: "Mon", opened: 42, resolved: 38 },
{ day: "Tue", opened: 57, resolved: 44 },
{ day: "Wed", opened: 35, resolved: 49 },
{ day: "Thu", opened: 61, resolved: 52 },
{ day: "Fri", opened: 48, resolved: 55 },
{ day: "Sat", opened: 19, resolved: 21 },
{ day: "Sun", opened: 14, resolved: 12 },
]
const config = {
opened: { label: "Opened", color: "var(--chart-1)" },
resolved: { label: "Resolved", color: "var(--chart-2)" },
} satisfies ChartConfig
return (
<ChartContainer config={config} className="aspect-auto h-64 w-full">
<BarChart data={data}>
<CartesianGrid vertical={false} />
<XAxis dataKey="day" tickLine={false} axisLine={false} tickMargin={8} />
<YAxis tickLine={false} axisLine={false} width={32} />
<ChartTooltip content={<ChartTooltipContent />} />
<ChartLegend content={<ChartLegendContent />} />
<Bar dataKey="opened" fill="var(--color-opened)" radius={2} />
<Bar dataKey="resolved" fill="var(--color-resolved)" radius={2} />
</BarChart>
</ChartContainer>
)
}Usage
import { Bar, BarChart, XAxis } from "recharts"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@zenoxai/ui/components/chart"
const config = {
opened: { label: "Opened", color: "var(--chart-1)" },
} satisfies ChartConfig
export function AlertsPerDay({ data }: { data: AlertsPerDayRow[] }) {
return (
<ChartContainer config={config} className="h-64 aspect-auto w-full">
<BarChart data={data}>
<XAxis dataKey="day" />
<ChartTooltip content={<ChartTooltipContent />} />
<Bar dataKey="opened" fill="var(--color-opened)" />
</BarChart>
</ChartContainer>
)
}
Recharts 3 is an optional peer dependency of @zenoxai/ui. Install it
in the app that renders charts:
pnpm add recharts
Import the chart parts from @zenoxai/ui/components/chart. No other subpath imports Recharts,
so apps without charts do not need it. The root entry, @zenoxai/ui, re-exports this module:
an app that imports from the root needs Recharts installed even if it renders no chart.
You write the chart with Recharts components; the library adds three things:
ChartContainermakes the chart responsive, styles the axes, grid and cursor with the theme tokens, and turns each key ofconfiginto a CSS variable,--color-<key>, scoped to the chart. Series reference it:fill="var(--color-opened)".ChartTooltipContentandChartLegendContentrender the tooltip and legend with the labels fromconfig, so the data can keep short keys.ChartConfigtypes the config.
Give ChartContainer a height. It defaults to a 16:9 aspect ratio; replace it with
aspect-auto and a height class for a fixed-height chart, or aspect-square for a donut.
Colors
Set color to one of the five chart tokens. They have light and dark values, so the chart
follows the theme with no extra config. Assign them in order, --chart-1 first, and keep the
same color for the same series across the charts of a page.
| Token | Color |
|---|---|
--chart-1 |
Blue, the brand primary |
--chart-2 |
Green |
--chart-3 |
Orange |
--chart-4 |
Gray |
--chart-5 |
Light blue, a deeper step of the brand accent on light |
--chart-line |
Red, for a line drawn over the series, such as a threshold |
Every chart color reaches 3:1 against the surface in both themes, as WCAG asks of a data mark.
For a series that needs different colors per theme outside the tokens, pass
theme: { light, dark } instead of color. Do not use the chart tokens for severity: severity
has its own scale, used by SeverityBadge.
Examples
Area chart
An area chart suits one series over a long period, such as alerts per day over two weeks.
minTickGap on the axis drops labels that would collide.
function ChartArea() {
const data = [
{ date: "Oct 10", alerts: 132 },
{ date: "Oct 11", alerts: 98 },
{ date: "Oct 12", alerts: 87 },
{ date: "Oct 13", alerts: 154 },
{ date: "Oct 14", alerts: 171 },
{ date: "Oct 15", alerts: 149 },
{ date: "Oct 16", alerts: 238 },
{ date: "Oct 17", alerts: 196 },
{ date: "Oct 18", alerts: 104 },
{ date: "Oct 19", alerts: 91 },
{ date: "Oct 20", alerts: 162 },
{ date: "Oct 21", alerts: 177 },
{ date: "Oct 22", alerts: 158 },
{ date: "Oct 23", alerts: 143 },
]
const config = {
alerts: { label: "Alerts", color: "var(--chart-1)" },
} satisfies ChartConfig
return (
<ChartContainer config={config} className="aspect-auto h-56 w-full">
<AreaChart data={data} margin={{ left: 8, right: 8 }}>
<CartesianGrid vertical={false} />
<XAxis
dataKey="date"
tickLine={false}
axisLine={false}
tickMargin={8}
minTickGap={24}
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent indicator="line" />}
/>
<Area
dataKey="alerts"
type="monotone"
fill="var(--color-alerts)"
fillOpacity={0.2}
stroke="var(--color-alerts)"
/>
</AreaChart>
</ChartContainer>
)
}Donut
A pie chart reads colors from the data: give each row a fill that points at its config key,
and pass nameKey to the tooltip and legend so they find the labels. A Label inside Pie
draws the total in the hole. Keep a donut to five slices or fewer.
function ChartDonut() {
const data = [
{ source: "endpoint", alerts: 412, fill: "var(--color-endpoint)" },
{ source: "identity", alerts: 167, fill: "var(--color-identity)" },
{ source: "email", alerts: 89, fill: "var(--color-email)" },
{ source: "cloud", alerts: 64, fill: "var(--color-cloud)" },
{ source: "network", alerts: 38, fill: "var(--color-network)" },
]
const config = {
alerts: { label: "Alerts" },
endpoint: { label: "Endpoint", color: "var(--chart-1)" },
identity: { label: "Identity", color: "var(--chart-2)" },
email: { label: "Email", color: "var(--chart-3)" },
cloud: { label: "Cloud", color: "var(--chart-4)" },
network: { label: "Network", color: "var(--chart-5)" },
} satisfies ChartConfig
const total = data.reduce((sum, item) => sum + item.alerts, 0)
return (
<ChartContainer config={config} className="aspect-square h-72">
<PieChart>
<ChartTooltip
content={<ChartTooltipContent nameKey="source" hideLabel />}
/>
<Pie data={data} dataKey="alerts" nameKey="source" innerRadius={60}>
<Label
content={({ viewBox }) => {
if (!viewBox || !("cx" in viewBox)) return null
const { cx = 0, cy = 0 } = viewBox
return (
<text x={cx} y={cy} textAnchor="middle">
<tspan
x={cx}
y={cy}
className="fill-foreground text-2xl font-semibold"
>
{total.toLocaleString("en-US")}
</tspan>
<tspan x={cx} y={cy + 20} className="fill-text-secondary">
Alerts
</tspan>
</text>
)
}}
/>
</Pie>
<ChartLegend content={<ChartLegendContent nameKey="source" />} />
</PieChart>
</ChartContainer>
)
}Accessibility
- Recharts 3 turns on its accessibility layer by default: the chart is focusable, and in bar, line and area charts the arrow keys move the tooltip between data points.
- A chart is not readable by every user. Put the key figure in text next to it, and offer the data as a Table or an export where the numbers matter.
- Color tells series apart only with a legend or a tooltip. Show the legend whenever there is more than one series.
API reference
Generated from the TypeScript source. className, style and children are accepted everywhere and omitted below.
ChartContainer
ChartContainer wraps one Recharts chart: it makes the chart responsive, styles it with the theme tokens and turns `config` into the `--color-<key>` variables its series use. Size it with a height or an aspect class; it defaults to aspect-video. For a single figure, use the StatTile pattern.
| Prop | Type | Default |
|---|---|---|
config* | ChartConfigLabel, icon and color of each data key of the chart. | - |
initialDimension | { width: number; height: number; }Size used until the container measures itself, as on the server. | { width: 320, height: 200 } as const |
ChartTooltip
The Tooltip component displays a floating box with data values when hovering over or clicking on chart elements.
No props of its own. Accepts the props of the element it renders.
ChartTooltipContent
ChartTooltipContent is the themed tooltip body: a heading, then a swatch, label and value per series, labeled from the chart config. Values are formatted with toLocaleString() in the browser's locale; pass `formatter` to format them in the app's locale.
| Prop | Type | Default |
|---|---|---|
hideIndicator | booleanHides the color swatch beside each series. | false |
hideLabel | booleanHides the heading, the label of the hovered point. | false |
indicator | "line" | "dot" | "dashed"Shape of the swatch: `dot` (default), `line` or `dashed`. | dot |
labelKey | stringConfig key whose label becomes the heading, or the payload field holding it. Defaults to the label of the hovered point. | - |
nameKey | stringConfig key that names each series, or the payload field holding it. Defaults to each item's name, then its data key. | - |
ChartLegend
ChartLegend is Recharts' Legend; pass `content={<ChartLegendContent />}` to render it in the theme.
No props of its own. Accepts the props of the element it renders.
ChartLegendContent
ChartLegendContent is the themed legend: a swatch and label per series.
| Prop | Type | Default |
|---|---|---|
hideIcon | booleanShows the color swatch even where the config sets an icon. | false |
nameKey | stringConfig key that names each series, or the payload field holding it. | - |
ChartStyle
ChartStyle writes the `--color-<key>` variables of a config for one chart. ChartContainer renders it already; use it only in a custom container.
| Prop | Type | Default |
|---|---|---|
config* | ChartConfig | - |
id* | string | - |