KPI stat
One number with its change in words, an optional target meter and sparkline. Plain SVG: no chart engine.
Install
It needs no chart engine: the whole install is a few small files of React and SVG.
Plain SVG (no chart engine)
npx shadcn@latest add https://beautifulcharts.dev/r/instrument-kpi-stat.jsonimport { KpiStat } from "@/components/beautiful-charts/themes/instrument/kpi-stat";
Example
A complete client module. Paste it into your app after installing the component.
"use client";
import { KpiStat } from "@/components/beautiful-charts/themes/instrument/kpi-stat";
// A change from a zero base is shown as an amount, never as an infinite percentage.
export function ActiveTeams() {
return (
<KpiStat
title="Active teams"
value={74}
previous={38}
previousLabel="last week"
target={80}
trend={[38, 42, 40, 47, 51, null, 55, 61, 63, 64, 69, 74]}
unit="teams with a weekly session"
/>
);
}
KPI stat props
Generated from KpiStatProps in the installed source, so this table and your editor's hints always agree. * marks a required prop.
| Prop | Type | Description |
|---|---|---|
| value* | number | null | The number. null or undefined shows the empty state. |
| previous | number | null | The comparison value, e.g. last period. |
| previousLabel | string | What the comparison is, e.g. "last month". |
| target | number | null | A goal. Adds a meter showing progress toward it. |
| trend | (number | null)[] | Recent values, oldest first, for the sparkline. Missing values break the line. |
| polarity | "up" | "down" | "up" when a rise is good (default), "down" for costs, churn, latency. |
| valueFormatter | (value: number) => string | Formats the number, change and target. |
| motion | MotionPreference | "full" (default), "subtle" or "off". Reduced-motion settings always win. |
| eyebrow | ReactNode | Small label above the title. |
| footer | ReactNode | A line or action at the bottom of the card. |
| title | ReactNode | Card title. A string also names the stat for screen readers. |
| description | ReactNode | A line under the title. |
| unit | ReactNode | Small unit or context under the number, e.g. "active teams". |
| loading | boolean | Show the loading state. |
| error | string | null | An error message; shows the error state. |
| density | Density | "comfortable" (default) or "compact". |
| className | string | Classes on the card. |
| palette | "theme" | "shadcn" | "theme" (default) or "shadcn" to use your --chart-N colours. |
| trendHeight | number | Sparkline height in px. |
Parts and styling hooks
Each part carries a data-slot attribute. Theme rules inside the chart are one attribute selector deep, so a selector such as .my-chart [data-slot="chart-title"] overrides them. These are the slots in the files this chart installs; parts a chart doesn't use are simply absent from its markup.
chartchart-bodychart-descriptionchart-eyebrowchart-footerchart-headerchart-headingchart-legendchart-legend-itemchart-legend-labelchart-legend-swatchchart-legend-valuechart-livechart-mainchart-plotchart-readoutchart-statchart-stat-notechart-stat-valuechart-statechart-state-boxchart-state-messagechart-statschart-tablechart-table-scrollchart-takeawaychart-titlechart-tooltipkpikpi-contextkpi-deltakpi-emptykpi-fillkpi-overkpi-rowkpi-targetkpi-target-labelkpi-trackkpi-trendodometerodometer-digitodometer-glyphsodometer-reelodometer-signpartial-notereadout-deltareadout-unitreadout-valuesr-onlyProps every chart shares
Header, states, legend, tooltip, formatting, motion, sound and controlled interaction state. They work the same on every Instrument chart and both engines. The guide explains colours, controlled state and accessibility.
Show all 2 shared props
| Prop | Type | Description |
|---|---|---|
| locale | string | BCP 47 locale. Defaults to "en-US" so server and client render the same text. |
| timeZone | string | IANA time zone for dates. Defaults to "UTC" so server and client agree. |