Kpis
<k.Kpis> draws tiles: a label, a figure, and optional lines under it.
There are two ways to fill them:
tiles={[…]}reads every tile from the first row of the result, one column per tile. Use it for a page's headline figures.each={{…}}draws one tile per row, naming the columns once. Use it when the number of tiles depends on the data, such as one per region.
A row of tiles has no title. Up to four sit side by side.
Example
totals.sql:
-- This month's headline figures
select 535 as sales, 50 as orders, 10.7 as avg_order,
12.5 as sales_change, 600 as sales_target, 0 as on_track,
'October 2026' as period
In report.tsx:
import totals from "./totals.sql?raw"
type Row = {
sales: number; orders: number; avg_order: number; sales_change: number
sales_target: number; on_track: number; period: string
}
// in defineReport({ … })
queries: { totals: query<Row>(totals) },
blocks: (k) => [
<k.Kpis
key="totals"
series="totals"
tiles={[
{ label: "Sales", value: "sales", delta: "sales_change", deltaLabel: "vs last month", target: "sales_target", status: "on_track" },
{ label: "Orders", value: "orders", period: "period" },
{ label: "Average order", value: "avg_order", format: "decimal", suffix: " kr" },
]}
/>,
],
Props
Also key and span (default 2): see
Props every block takes. Kpis takes no
title, description or info.
A tile in tiles
The spec in each
Example: one tile per row
regions.sql:
-- Sales per region
select 'North' as region, 120 as sales
union all select 'South', 95
union all select 'East', 180
In report.tsx:
import regions from "./regions.sql?raw"
// in defineReport({ … })
queries: { regions: query<{ region: string; sales: number }>(regions) },
blocks: (k) => [
<k.Kpis key="regions" series="regions" each={{ label: "region", value: "sales" }} />,
],
Checks
A series that is not one of the report's queries, or a column it names
that the query's rows do not have, fails the typecheck, as does a missing
required prop or a value of the wrong type.