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.

NameTypeRequiredDefaultMeaning
seriesquery nameyesThe query the figures come from
tileslist of tilesone of tiles or eachTiles read off the first row
eachone tile specone of tiles or eachOne tile per row
drillslist of drillsnoThe drills a tile's drill names

A tile in tiles

KeyTypeRequiredMeaning
labeltextyes, unless metricThe tile's label
valuecolumnyes, unless metricThe figure
format, prefix, suffix, decimalsformatnoHow the figure prints
linecolumnnoOne muted line under the figure
periodcolumnnoThe period the tile covers, set smaller
deltacolumnnoA change in percent (12.5 for 12.5 %), printed whole and signed beside the figure: "+13 %", green when up
deltaLabeltextnoThe words after the delta, such as "vs last month"
targetcolumnnoPrinted as "/ target" after the figure
statuscolumnnoA yes/no column: "On track" in green, or "Off track" muted
metricmetric namenoA named metric: its label and format, and its value when value is left out
drilldrill namenoThe tile's click opens the drill of this name in drills

The spec in each

KeyTypeRequiredMeaning
labelcolumnyesEach tile's label
valuecolumnyesEach tile's figure
format, prefix, suffix, decimalsformatnoHow the figures print
formatColumncolumnnoA text column naming each row's format, for tiles in mixed units
line, period, target, statuscolumnnoAs in tiles
trendcolumnnoA list of {value} points, oldest first: the tile becomes a card with a sparkline
directioncolumnnoup (the default) or down: which way is good, with trend
approxcolumnnoA yes/no column: a badge saying the definition is not settled
definitioncolumnnoA text column shown in the card's "(i)"

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.