Linked KPI Board
KPI sparklines that cross-filter on a shared selection — hover one, all highlight — under a meta-narrative reading across them. The reasoning dashboard.
Acme · FP&A scorecard
3 of 3 KPIs improved MoM — MRR led (+8.0%).
MRR
$5.92M
latest
Gross margin
71.0%
latest
Headcount
267
latest
| Month | MRR | Gross margin | Headcount |
|---|---|---|---|
| Jul 2025 | $3.2M | 58.0% | 142 |
| Aug 2025 | $3.42M | 59.0% | 148 |
| Sep 2025 | $3.68M | 60.0% | 156 |
| Oct 2025 | $4.05M | 62.0% | 171 |
| Nov 2025 | $4.38M | 64.0% | 185 |
| Dec 2025 | $4.62M | 65.0% | 198 |
| Jan 2026 | $4.85M | 66.0% | 210 |
| Feb 2026 | $5.12M | 67.0% | 224 |
| Mar 2026 | $5.34M | 68.0% | 235 |
| Apr 2026 | $5.58M | 69.0% | 248 |
| May 2026 | $5.48M | 70.0% | 251 |
| Jun 2026 | $5.92M | 71.0% | 267 |
hover any panel — the guide and values move across all of them
Acme analytics · updated hourly
Demo data is illustrative. Replace with your own typed data prop.
When to reach for it
A board of KPIs read in isolation hides the relationships between them. Use the Linked KPI Board when the cross-reading is the insight — revenue is up but cash shortened the same month. Hover any panel and a guide plus the shown value move across all of them, and a single meta-narrative reads the whole board at once.
How it thinks
- Shared selection — every panel reads one
useSelection()context. Hovering a month setsactiveX(via Recharts'activeLabel), and all panels mark it and switch their headline to that month's value. That's linked brushing, with no per-panel wiring. - Meta-narrative —
summarizeBoard()reads the per-KPIdeltainsights and synthesizes one sentence: "2 of 3 KPIs improved MoM — Revenue led (+8.1%)." It reasons across widgets, not within one.
The selection-kit provider follows the same no-op-safe discipline as Studio: outside a provider the hook returns inert defaults, so a panel renders standalone with zero overhead.
Good to know
- Pass any number of aligned monthly KPIs (
format: "currency" | "percent" | "number"); they just need to share themonthsaxis. - The cross-filter is a hover enhancement; the accessible representation is the data table (months × KPIs), and the meta-narrative is the figure's label.
selection-kitis reusable on its own — wrap any group of time-aligned widgets to make them cross-filter.
Use it with your data
Data contract
Passed via the data prop — an object: validated at runtime, so a bad shape degrades to the error state rather than crashing.
| Field | Type | Required |
|---|---|---|
| months | array | yes |
| kpis | array | yes |
Static props
import { ChartLinkedBoard } from "@/components/blocks/chart-linked-board/chart-linked-board";
<ChartLinkedBoard data={myData} />Client fetch (SWR)
const { data, isLoading, error } = useSWR("/api/metric", fetcher);
// route isLoading/error through <ChartStates>, then:
<ChartLinkedBoard data={data} />Server component (RSC)
const data = await fetch(url, {
next: { revalidate: 3600 },
}).then((r) => r.json());
<ChartLinkedBoard data={data} />Map arbitrary rows
import { mapRows } from "@/lib/sextant/column-map";
const out = mapRows(rows, { /* contractKey: "yourColumn" */ }, dataSchema);
if (out.ok) <ChartLinkedBoard data={out.data} />;Make it yours
Every customization below is an optional prop — omit them all and you get the defaults shown above. Recolor, reformat, restyle, or drop the card chrome without forking the component.
Format numbers (currency / locale)
const eur = new Intl.NumberFormat("de-DE", {
style: "currency",
currency: "EUR",
notation: "compact",
});
<ChartLinkedBoard valueFormatter={(n) => eur.format(n)} />Replace or hide the narrative
<ChartLinkedBoard narrative="Your own one-liner." />
// …or hide it entirely:
<ChartLinkedBoard narrative={false} />Restyle any region
<ChartLinkedBoard
className="max-w-xl"
classNames={{ body: "bg-muted/20" }}
/>Drop the card chrome (headless plot)
import { ChartLinkedBoardPlot } from "@/components/blocks/chart-linked-board/chart-linked-board";
// Just the chart body — bring your own card/layout:
<ChartLinkedBoardPlot data={data} config={config} />