A KPI card computed from a series: the latest value, its change against a baseline in the same data, and a sparkline of the series.
KPI grid
A responsive grid of trend cards drawn from one dataset: each metric shows its latest value, its change and a sparkline.
- Use when
- The top of a dashboard or a monthly report: three to eight metrics tracked over the same periods. Pass one table with a column per metric.
- Avoid when
- Figures without a series behind them (use stat row); comparing the metrics with each other on one scale (use line chart or small multiples); more than eight cards.
Preview
Monthly active users rose from 38,100 to 48,200 in 2024
Four product metrics by month, 2024. Change is measured against January.
Source: Internal analytics · Illustrative data
import { KpiGrid } from "insightcharts";
const data = [
{ month: "2024-01-01", users: 38100, mrr: 412000, churn: 4.2, response: 4.1 },
{ month: "2024-02-01", users: 39400, mrr: 428000, churn: 4, response: 3.9 },
{ month: "2024-03-01", users: 39000, mrr: 431000, churn: 3.9, response: 3.8 },
{ month: "2024-04-01", users: 40800, mrr: 455000, churn: 3.6, response: 3.2 },
{ month: "2024-05-01", users: 42200, mrr: 470000, churn: 3.1, response: 3.4 },
{ month: "2024-06-01", users: 41900, mrr: 476000, churn: 2.9, response: 2.9 },
{ month: "2024-07-01", users: 43500, mrr: 498000, churn: 2.6, response: 2.7 },
{ month: "2024-08-01", users: 44100, mrr: 511000, churn: 2.4, response: 2.6 },
{ month: "2024-09-01", users: 44500, mrr: 524000, churn: 2.3, response: 2.4 },
{ month: "2024-10-01", users: 46000, mrr: 548000, churn: 2.2, response: 2.3 },
{ month: "2024-11-01", users: 47200, mrr: 566000, churn: 2.1, response: 2.2 },
{ month: "2024-12-01", users: 48200, mrr: 589000, churn: 2.1, response: 2.2 },
];
export function Example() {
return (
<KpiGrid
title="Monthly active users rose from 38,100 to 48,200 in 2024"
subtitle="Four product metrics by month, 2024. Change is measured against January."
data={data}
x="month"
metrics={[
{ y: "users", label: "Monthly active users", format: "compact" },
{ y: "mrr", label: "Recurring revenue", format: "currency:USD" },
{
y: "churn",
label: "Monthly churn",
format: "{value}%",
change: "absolute",
upIsGood: false,
},
{
y: "response",
label: "Median first response",
format: "{value} h",
upIsGood: false,
},
]}
source="Internal analytics"
note="Illustrative data"
/>
);
}{
"type": "kpi-grid",
"title": "Monthly active users rose from 38,100 to 48,200 in 2024",
"subtitle": "Four product metrics by month, 2024. Change is measured against January.",
"data": [
{
"month": "2024-01-01",
"users": 38100,
"mrr": 412000,
"churn": 4.2,
"response": 4.1
},
{
"month": "2024-02-01",
"users": 39400,
"mrr": 428000,
"churn": 4,
"response": 3.9
},
{
"month": "2024-03-01",
"users": 39000,
"mrr": 431000,
"churn": 3.9,
"response": 3.8
},
{
"month": "2024-04-01",
"users": 40800,
"mrr": 455000,
"churn": 3.6,
"response": 3.2
},
{
"month": "2024-05-01",
"users": 42200,
"mrr": 470000,
"churn": 3.1,
"response": 3.4
},
{
"month": "2024-06-01",
"users": 41900,
"mrr": 476000,
"churn": 2.9,
"response": 2.9
},
{
"month": "2024-07-01",
"users": 43500,
"mrr": 498000,
"churn": 2.6,
"response": 2.7
},
{
"month": "2024-08-01",
"users": 44100,
"mrr": 511000,
"churn": 2.4,
"response": 2.6
},
{
"month": "2024-09-01",
"users": 44500,
"mrr": 524000,
"churn": 2.3,
"response": 2.4
},
{
"month": "2024-10-01",
"users": 46000,
"mrr": 548000,
"churn": 2.2,
"response": 2.3
},
{
"month": "2024-11-01",
"users": 47200,
"mrr": 566000,
"churn": 2.1,
"response": 2.2
},
{
"month": "2024-12-01",
"users": 48200,
"mrr": 589000,
"churn": 2.1,
"response": 2.2
}
],
"x": "month",
"metrics": [
{ "y": "users", "label": "Monthly active users", "format": "compact" },
{ "y": "mrr", "label": "Recurring revenue", "format": "currency:USD" },
{
"y": "churn",
"label": "Monthly churn",
"format": "{value}%",
"change": "absolute",
"upIsGood": false
},
{
"y": "response",
"label": "Median first response",
"format": "{value} h",
"upIsGood": false
}
],
"source": "Internal analytics",
"note": "Illustrative data"
}Render with import { Insight } from "insightcharts" and <Insight spec={spec} />. Nested specs resolve by name; nothing else to import.
Composition
Built from
The pieces kpi grid renders or expects in its slots.
Molecules
1Atoms
2A word-sized line or bar series without axes, with the latest point marked.
A compact, bordered KPI tile: label, value, change against a baseline and an optional trend graphic.
Used in
1 template uses kpi grid.
Templates
1A recurring report. It has a masthead with the report name and period, a headline that states the main change, a KPI grid with changes and sparklines, a ranked list beside one or two trend charts, an optional comparison table, short notes and a source note.
Installation
$ pnpm dlx shadcn@latest add @insightcharts/kpi-gridThe CLI copies the source into your project and needs the @insightcharts registry in components.json; see installation. With the package, import it by name:
import { KpiGrid } from "insightcharts";Examples
Two columns, change against the previous month
import { KpiGrid } from "insightcharts";
const data = [
{ month: "2024-01-01", users: 38100, mrr: 412000, churn: 4.2, response: 4.1 },
{ month: "2024-02-01", users: 39400, mrr: 428000, churn: 4, response: 3.9 },
{ month: "2024-03-01", users: 39000, mrr: 431000, churn: 3.9, response: 3.8 },
{ month: "2024-04-01", users: 40800, mrr: 455000, churn: 3.6, response: 3.2 },
{ month: "2024-05-01", users: 42200, mrr: 470000, churn: 3.1, response: 3.4 },
{ month: "2024-06-01", users: 41900, mrr: 476000, churn: 2.9, response: 2.9 },
{ month: "2024-07-01", users: 43500, mrr: 498000, churn: 2.6, response: 2.7 },
{ month: "2024-08-01", users: 44100, mrr: 511000, churn: 2.4, response: 2.6 },
{ month: "2024-09-01", users: 44500, mrr: 524000, churn: 2.3, response: 2.4 },
{ month: "2024-10-01", users: 46000, mrr: 548000, churn: 2.2, response: 2.3 },
{ month: "2024-11-01", users: 47200, mrr: 566000, churn: 2.1, response: 2.2 },
{ month: "2024-12-01", users: 48200, mrr: 589000, churn: 2.1, response: 2.2 },
];
export function Example() {
return (
<KpiGrid
data={data}
x="month"
compare="previous"
columns={2}
metrics={[
{ y: "users", label: "Monthly active users", format: "compact" },
{ y: "mrr", label: "Recurring revenue", format: "currency:USD", kind: "bar" },
]}
/>
);
}{
"type": "kpi-grid",
"data": [
{
"month": "2024-01-01",
"users": 38100,
"mrr": 412000,
"churn": 4.2,
"response": 4.1
},
{
"month": "2024-02-01",
"users": 39400,
"mrr": 428000,
"churn": 4,
"response": 3.9
},
{
"month": "2024-03-01",
"users": 39000,
"mrr": 431000,
"churn": 3.9,
"response": 3.8
},
{
"month": "2024-04-01",
"users": 40800,
"mrr": 455000,
"churn": 3.6,
"response": 3.2
},
{
"month": "2024-05-01",
"users": 42200,
"mrr": 470000,
"churn": 3.1,
"response": 3.4
},
{
"month": "2024-06-01",
"users": 41900,
"mrr": 476000,
"churn": 2.9,
"response": 2.9
},
{
"month": "2024-07-01",
"users": 43500,
"mrr": 498000,
"churn": 2.6,
"response": 2.7
},
{
"month": "2024-08-01",
"users": 44100,
"mrr": 511000,
"churn": 2.4,
"response": 2.6
},
{
"month": "2024-09-01",
"users": 44500,
"mrr": 524000,
"churn": 2.3,
"response": 2.4
},
{
"month": "2024-10-01",
"users": 46000,
"mrr": 548000,
"churn": 2.2,
"response": 2.3
},
{
"month": "2024-11-01",
"users": 47200,
"mrr": 566000,
"churn": 2.1,
"response": 2.2
},
{
"month": "2024-12-01",
"users": 48200,
"mrr": 589000,
"churn": 2.1,
"response": 2.2
}
],
"x": "month",
"compare": "previous",
"columns": 2,
"metrics": [
{ "y": "users", "label": "Monthly active users", "format": "compact" },
{
"y": "mrr",
"label": "Recurring revenue",
"format": "currency:USD",
"kind": "bar"
}
]
}Render with import { Insight } from "insightcharts" and <Insight spec={spec} />. Nested specs resolve by name; nothing else to import.
Six metrics
Illustrative data
import { KpiGrid } from "insightcharts";
const data = [
{
month: "2024-01-01",
users: 38100,
mrr: 412000,
churn: 4.2,
response: 4.1,
nps: 31,
tickets: 1840,
},
{
month: "2024-02-01",
users: 39400,
mrr: 428000,
churn: 4,
response: 3.9,
nps: 32,
tickets: 1778,
},
{
month: "2024-03-01",
users: 39000,
mrr: 431000,
churn: 3.9,
response: 3.8,
nps: 34,
tickets: 1716,
},
{
month: "2024-04-01",
users: 40800,
mrr: 455000,
churn: 3.6,
response: 3.2,
nps: 35,
tickets: 1654,
},
{
month: "2024-05-01",
users: 42200,
mrr: 470000,
churn: 3.1,
response: 3.4,
nps: 37,
tickets: 1592,
},
{
month: "2024-06-01",
users: 41900,
mrr: 476000,
churn: 2.9,
response: 2.9,
nps: 38,
tickets: 1530,
},
{
month: "2024-07-01",
users: 43500,
mrr: 498000,
churn: 2.6,
response: 2.7,
nps: 39,
tickets: 1468,
},
{
month: "2024-08-01",
users: 44100,
mrr: 511000,
churn: 2.4,
response: 2.6,
nps: 41,
tickets: 1406,
},
{
month: "2024-09-01",
users: 44500,
mrr: 524000,
churn: 2.3,
response: 2.4,
nps: 42,
tickets: 1344,
},
{
month: "2024-10-01",
users: 46000,
mrr: 548000,
churn: 2.2,
response: 2.3,
nps: 44,
tickets: 1282,
},
{
month: "2024-11-01",
users: 47200,
mrr: 566000,
churn: 2.1,
response: 2.2,
nps: 45,
tickets: 1220,
},
{
month: "2024-12-01",
users: 48200,
mrr: 589000,
churn: 2.1,
response: 2.2,
nps: 46,
tickets: 1158,
},
];
export function Example() {
return (
<KpiGrid
data={data}
x="month"
metrics={[
{ y: "users", label: "Monthly active users", format: "compact" },
{ y: "mrr", label: "Recurring revenue", format: "currency:USD" },
{
y: "churn",
label: "Monthly churn",
format: "{value}%",
change: "absolute",
upIsGood: false,
},
{
y: "response",
label: "Median first response",
format: "{value} h",
upIsGood: false,
},
{ y: "nps", label: "Net promoter score", change: "absolute" },
{ y: "tickets", label: "Support tickets", upIsGood: false },
]}
note="Illustrative data"
/>
);
}{
"type": "kpi-grid",
"data": [
{
"month": "2024-01-01",
"users": 38100,
"mrr": 412000,
"churn": 4.2,
"response": 4.1,
"nps": 31,
"tickets": 1840
},
{
"month": "2024-02-01",
"users": 39400,
"mrr": 428000,
"churn": 4,
"response": 3.9,
"nps": 32,
"tickets": 1778
},
{
"month": "2024-03-01",
"users": 39000,
"mrr": 431000,
"churn": 3.9,
"response": 3.8,
"nps": 34,
"tickets": 1716
},
{
"month": "2024-04-01",
"users": 40800,
"mrr": 455000,
"churn": 3.6,
"response": 3.2,
"nps": 35,
"tickets": 1654
},
{
"month": "2024-05-01",
"users": 42200,
"mrr": 470000,
"churn": 3.1,
"response": 3.4,
"nps": 37,
"tickets": 1592
},
{
"month": "2024-06-01",
"users": 41900,
"mrr": 476000,
"churn": 2.9,
"response": 2.9,
"nps": 38,
"tickets": 1530
},
{
"month": "2024-07-01",
"users": 43500,
"mrr": 498000,
"churn": 2.6,
"response": 2.7,
"nps": 39,
"tickets": 1468
},
{
"month": "2024-08-01",
"users": 44100,
"mrr": 511000,
"churn": 2.4,
"response": 2.6,
"nps": 41,
"tickets": 1406
},
{
"month": "2024-09-01",
"users": 44500,
"mrr": 524000,
"churn": 2.3,
"response": 2.4,
"nps": 42,
"tickets": 1344
},
{
"month": "2024-10-01",
"users": 46000,
"mrr": 548000,
"churn": 2.2,
"response": 2.3,
"nps": 44,
"tickets": 1282
},
{
"month": "2024-11-01",
"users": 47200,
"mrr": 566000,
"churn": 2.1,
"response": 2.2,
"nps": 45,
"tickets": 1220
},
{
"month": "2024-12-01",
"users": 48200,
"mrr": 589000,
"churn": 2.1,
"response": 2.2,
"nps": 46,
"tickets": 1158
}
],
"x": "month",
"metrics": [
{ "y": "users", "label": "Monthly active users", "format": "compact" },
{ "y": "mrr", "label": "Recurring revenue", "format": "currency:USD" },
{
"y": "churn",
"label": "Monthly churn",
"format": "{value}%",
"change": "absolute",
"upIsGood": false
},
{
"y": "response",
"label": "Median first response",
"format": "{value} h",
"upIsGood": false
},
{ "y": "nps", "label": "Net promoter score", "change": "absolute" },
{ "y": "tickets", "label": "Support tickets", "upIsGood": false }
],
"note": "Illustrative data"
}Render with import { Insight } from "insightcharts" and <Insight spec={spec} />. Nested specs resolve by name; nothing else to import.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| data | DataInput | One dataset in time order: a row per period, a column per metric. Omit inside <InsightData>. | |
| transform | Transform | Transform[] | Applied once, before the cards read the rows. | |
| x | string | Period field shared by every metric. | |
| metricsrequired | { y, label, format?, upIsGood?, compare?, change?, deltaFormat?, deltaLabel?, context?, value?, delta?, kind? }[] | One card per metric; each takes the props of trend-card. | |
| compare | "first" | "previous" | number | "first" | Baseline for every card's change; a metric can override it. |
| change | "percent" | "absolute" | "percent" | Relative change, or the difference between the two values; a metric can override it. An absolute change of a percentage metric is shown in percentage points ("−2.1 pts"). |
| deltaFormat | FormatSpec | Format for absolute changes, e.g. "{value} pp" or "signed"; a metric can override it. Default: points for percentage formats, otherwise the metric's own format. | |
| columns | number | Preferred columns. Default: up to four, reduced to fit cards of at least 200px. | |
| title | string | The main fact with its number, or a plain label. | |
| subtitle | string | Population and period. | |
| source | string | Shown under the grid as "Source: ...". | |
| note | string | Method note or caveat. | |
| theme | ThemeInput | Theme override, passed on to every card. | |
| mode | "auto" | "system" | "light" | "dark" | "auto" | Colour mode. |