Data display
Chart
Dependency-free SVG charts that follow the theme: line, area (plain or stacked), bar (single, grouped or stacked), donut and sparkline. Series take the chart-1 to chart-4 colors in a fixed order, starting with the accent. Every chart has a hover and keyboard tooltip, a legend when there are two or more series, a written summary and a hidden data table for screen readers.
On this page
Revenue and expenses
$412,980
Fiscal year 2025
- Revenue
- Expenses
| Month | Revenue | Expenses |
|---|---|---|
| Jan | $24.8k | $19.2k |
| Feb | $26.2k | $20.1k |
| Mar | $29.9k | $21.4k |
| Apr | $31.4k | $22k |
| May | $30.1k | $23.8k |
| Jun | $34.8k | $23.1k |
| Jul | $36.9k | $24.9k |
| Aug | $35.2k | $26.4k |
| Sep | $38.4k | $25.2k |
| Oct | $41.2k | $27.1k |
| Nov | $39.8k | $28.8k |
| Dec | $44.3k | $29.6k |
<AreaChart title="Revenue and expenses, 2025" indexLabel="Month" labels={["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]} format="usd" height={240} series={[ { name: "Revenue", color: 1, values: [24800, 26200, 29900, 31400, 30100, 34800, 36900, 35200, 38400, 41200, 39800, 44280] }, { name: "Expenses", color: 2, values: [19200, 20100, 21400, 22000, 23800, 23100, 24900, 26400, 25200, 27100, 28800, 29600] }, ]}/>Usage
import { AreaChart, BarChart, Donut, LineChart, Sparkline } from "@/components/ui/chart"; <LineChart title="Monthly revenue" labels={["Jan", "Feb", "Mar"]} series={[{ name: "Revenue", color: 1, values: [24800, 26200, 29900] }]} format="usd"/>Examples
Overview
Revenue and expenses
$412,980
Fiscal year 2025
- Revenue
- Expenses
| Month | Revenue | Expenses |
|---|---|---|
| Jan | $24.8k | $19.2k |
| Feb | $26.2k | $20.1k |
| Mar | $29.9k | $21.4k |
| Apr | $31.4k | $22k |
| May | $30.1k | $23.8k |
| Jun | $34.8k | $23.1k |
| Jul | $36.9k | $24.9k |
| Aug | $35.2k | $26.4k |
| Sep | $38.4k | $25.2k |
| Oct | $41.2k | $27.1k |
| Nov | $39.8k | $28.8k |
| Dec | $44.3k | $29.6k |
<AreaChart title="Revenue and expenses, 2025" indexLabel="Month" labels={["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]} format="usd" height={240} series={[ { name: "Revenue", color: 1, values: [24800, 26200, 29900, 31400, 30100, 34800, 36900, 35200, 38400, 41200, 39800, 44280] }, { name: "Expenses", color: 2, values: [19200, 20100, 21400, 22000, 23800, 23100, 24900, 26400, 25200, 27100, 28800, 29600] }, ]}/>Line
Up to four series on one y-axis. dots marks every point, which helps with fewer than about 20 points. Focus the chart and use the arrow keys to step through values.
- p50
- p95
- p99
| Day | p50 | p95 | p99 |
|---|---|---|---|
| Oct 1 | 118ms | 342ms | 610ms |
| Oct 2 | 121ms | 360ms | 655ms |
| Oct 3 | 116ms | 338ms | 600ms |
| Oct 4 | 124ms | 395ms | 720ms |
| Oct 5 | 130ms | 420ms | 790ms |
| Oct 6 | 127ms | 398ms | 742ms |
| Oct 7 | 119ms | 351ms | 640ms |
| Oct 8 | 115ms | 340ms | 612ms |
| Oct 9 | 122ms | 366ms | 668ms |
| Oct 10 | 126ms | 380ms | 702ms |
| Oct 11 | 131ms | 412ms | 760ms |
| Oct 12 | 128ms | 401ms | 731ms |
| Oct 13 | 120ms | 358ms | 650ms |
| Oct 14 | 117ms | 349ms | 628ms |
<LineChart title="API latency" indexLabel="Day" labels={["Oct 1", "Oct 2", "Oct 3", "Oct 4", "Oct 5", "Oct 6", "Oct 7", "Oct 8", "Oct 9", "Oct 10", "Oct 11", "Oct 12", "Oct 13", "Oct 14"]} format="ms" dots series={[ { name: "p50", color: 1, values: [118, 121, 116, 124, 130, 127, 119, 115, 122, 126, 131, 128, 120, 117] }, { name: "p95", color: 2, values: [342, 360, 338, 395, 420, 398, 351, 340, 366, 380, 412, 401, 358, 349] }, { name: "p99", color: 4, values: [610, 655, 600, 720, 790, 742, 640, 612, 668, 702, 760, 731, 650, 628] }, ]}/>Stacked area
stacked shows parts of a whole over time. The tooltip lists each part and the total.
- Organic
- Referral
- Paid
| Week | Organic | Referral | Paid |
|---|---|---|---|
| W1 | 8.2k | 3.1k | 2.4k |
| W2 | 8.6k | 3.3k | 2.6k |
| W3 | 9.1k | 3.2k | 3.5k |
| W4 | 9.4k | 3.7k | 3.3k |
| W5 | 9.9k | 3.9k | 2.9k |
| W6 | 10.4k | 4.1k | 2.7k |
| W7 | 10.2k | 4.5k | 3.1k |
| W8 | 10.9k | 4.4k | 3.6k |
| W9 | 11.5k | 4.8k | 3.4k |
| W10 | 12.1k | 5.1k | 3.2k |
<AreaChart stacked title="Visits by channel" indexLabel="Week" labels={["W1", "W2", "W3", "W4", "W5", "W6", "W7", "W8", "W9", "W10"]} format="compact" series={[ { name: "Organic", color: 1, values: [8200, 8600, 9100, 9400, 9900, 10400, 10200, 10900, 11500, 12100] }, { name: "Referral", color: 2, values: [3100, 3300, 3200, 3700, 3900, 4100, 4500, 4400, 4800, 5100] }, { name: "Paid", color: 3, values: [2400, 2600, 3500, 3300, 2900, 2700, 3100, 3600, 3400, 3200] }, ]}/>Bar
The single-series shorthand takes values, name and color. Labels thin out on narrow screens; the tooltip always shows the full label.
| Label | Signups |
|---|---|
| Sep 1 | 42 |
| Sep 2 | 38 |
| Sep 3 | 51 |
| Sep 4 | 47 |
| Sep 5 | 55 |
| Sep 6 | 61 |
| Sep 7 | 33 |
| Sep 8 | 29 |
| Sep 9 | 58 |
| Sep 10 | 64 |
| Sep 11 | 70 |
| Sep 12 | 66 |
| Sep 13 | 72 |
| Sep 14 | 41 |
| Sep 15 | 36 |
| Sep 16 | 69 |
| Sep 17 | 75 |
| Sep 18 | 81 |
| Sep 19 | 77 |
| Sep 20 | 84 |
| Sep 21 | 47 |
| Sep 22 | 40 |
| Sep 23 | 79 |
| Sep 24 | 88 |
| Sep 25 | 92 |
| Sep 26 | 86 |
| Sep 27 | 95 |
| Sep 28 | 52 |
| Sep 29 | 48 |
| Sep 30 | 101 |
const signups = [42, 38, 51, 47, 55, 61, 33, 29, 58, 64, 70, 66, 72, 41, 36, 69, 75, 81, 77, 84, 47, 40, 79, 88, 92, 86, 95, 52, 48, 101]; <BarChart name="Signups" labels={signups.map((_, i) => `Sep ${i + 1}`)} values={signups} height={220}/>Grouped and stacked bars
Pass series for several bars per label: side by side by default, or stacked with a 2 px gap between segments.
- Europe
- Americas
| Quarter | Europe | Americas |
|---|---|---|
| Q1 | $141k | $182k |
| Q2 | $151k | $202k |
| Q3 | $149k | $214k |
| Q4 | $172k | $236k |
- Starter
- Team
- Business
| Month | Starter | Team | Business |
|---|---|---|---|
| Jul | 420 | 210 | 60 |
| Aug | 450 | 230 | 64 |
| Sep | 470 | 260 | 71 |
| Oct | 510 | 280 | 78 |
| Nov | 530 | 310 | 84 |
| Dec | 560 | 335 | 92 |
<BarChart title="Revenue by region" indexLabel="Quarter" labels={["Q1", "Q2", "Q3", "Q4"]} format="usd" series={[ { name: "Europe", color: 1, values: [141020, 150880, 149300, 171640] }, { name: "Americas", color: 2, values: [182400, 201950, 214300, 236120] }, ]}/><BarChart stacked title="Accounts by plan" indexLabel="Month" labels={["Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]} series={[ { name: "Starter", color: 1, values: [420, 450, 470, 510, 530, 560] }, { name: "Team", color: 2, values: [210, 230, 260, 280, 310, 335] }, { name: "Business", color: 3, values: [60, 64, 71, 78, 84, 92] }, ]}/>Donut
Part-to-whole with up to four segments. The legend prints each share (and value with format); hover a segment or legend row to show it in the middle.
- Team$21.9k45%
- Business$14.6k30%
- Starter$8,20017%
- Add-ons$3,5107%
<Donut title="Revenue by plan" format="usd" centerLabel="This month" centerValue="$48.2k" segments={[ { label: "Team", value: 21900, color: 1 }, { label: "Business", value: 14600, color: 2 }, { label: "Starter", value: 8200, color: 3 }, { label: "Add-ons", value: 3510, color: 4 }, ]}/>Sparkline
A tiny trend for table rows and stat tiles. It is decorative on its own; pass labels to get a hover tooltip.
{accounts.map((a) => ( <div key={a.name} className="grid grid-cols-[1fr_96px_auto] items-center gap-4 px-4 py-3"> <span className="truncate text-[13px] font-medium">{a.name}</span> <Sparkline values={a.trend} labels={["Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct"]} format="usd" area height={28} /> <span className="font-mono text-[13px] tabular-nums">{a.mrr}</span> </div>))}Props
LineChart and AreaChart
labelsrequiredstring[]default: —One label per point (x-axis).seriesrequired{ name: string; color: 1 | 2 | 3 | 4; values: number[] }[]default: —Up to four series.titlestringdefault: —Used in the spoken summary and as the data table caption.indexLabelstringdefault: —Header of the label column in the data table, e.g. "Month".format"number" | "compact" | "percent" | "ms" | "usd" | (v) => stringdefault: "number"Axis and tooltip format. Functions only from client components.heightnumberdefault: 220Height in px; width fills the container.areabooleandefault: false (AreaChart: true)Gradient fill under each line.stackedbooleandefault: falseStack series as areas.curve"linear" | "monotone"default: "linear" (AreaChart: "monotone")Straight segments or a smooth curve that never overshoots.dotsbooleandefault: falseDot on every point.zerobooleandefault: trueStart the y-axis at 0. Set false for values in a narrow band.
BarChart
labelsrequiredstring[]default: —One label per bar or group.values / name / colornumber[] / string / 1–4default: —Single-series shorthand.seriesLineSeries[]default: —Two to four series, grouped or stacked.stackedbooleandefault: falseStack series in one bar.title, indexLabel, format, height—default: —As for LineChart (height defaults to 200).
Donut
segmentsrequired{ label: string; value: number; color: 1 | 2 | 3 | 4 }[]default: —Up to four parts.titlestringdefault: "Donut chart"Accessible title.centerValue / centerLabelstringdefault: —Shown in the hole when nothing is hovered.formatChartFormatdefault: —Also show each value in the legend.size / thicknessnumberdefault: 140 / 14Diameter and ring width in px.legend"right" | "bottom" | "none"default: "right"Legend placement.
Sparkline and Legend
valuesrequirednumber[]default: —Sparkline points.labelsstring[]default: —Enables the hover tooltip.color1 | 2 | 3 | 4default: 1Series color.areabooleandefault: falseSoft fill under the line.heightnumberdefault: 32Height in px.Legend items{ label: string; color: 1 | 2 | 3 | 4 }[]default: —Standalone legend for custom layouts.
Accessibility
- Each chart is a focusable group with role description "chart" and a generated summary: the range, and for each series where it starts, ends and peaks.
- A visually hidden table holds every value, so screen reader users can read the data in table mode.
- With the chart focused, Left and Right arrows step through points, Home and End jump to the ends, and Escape clears; the current values are announced.
- Series are told apart by legend labels and the tooltip, never by color alone; all text uses text colors, not series colors.
- Colors come from chart-1 to chart-4 tokens and adapt to the accent and to dark mode; the grid and axes stay recessive.