Props every block takes

These props work on most blocks. Each block's page lists only its own; it says when one of these does not apply.

Props

NameTypeRequiredDefaultMeaning
keytextyesReact's key: any name unique on the page
titletext, or a function of the datayes on chartsThe heading over the block
descriptiontext, or a function of the datanoOne muted line under the title
span1 or 2nothe block's own1 takes half the row, 2 the whole row. Two span={1} blocks in a row sit side by side
info{ what, rules, sql }noThe block's own "(i)": what is one line (required), rules a list of rule ids from the dialog, sql a list of query names
metricmetric namenoA named metric on charts: its label, its format, and its value when y is left out

A function of the data reads the report's results: title={(d) => String(d.totals[0].label)} is the label column of the first row of totals.

Default spans: charts (Line, StackedBars, CategoryBars, BarsWithLine, Treemap, Funnel, ShareBars, Waffle) take half a row; Kpis, Table, GroupedTable, Progress, DriverTree, Section, Tabs and Inputs take the whole row.

When and Repeat

Two wrappers from ~/components/report-kit/block-wrap go around a block:

WrapperPropsDoes
Whenwhen, spanDraws the block inside only while when holds: a function of the data ((d) => d.flags[0].show), an input's name (drawn while it is on), or { input, is } / { input, not } (is and not take a value or a list)
Repeatseries, by, label, title, itemSpan, drawOne copy of a block per value of the by column of series, each on that value's rows. draw(title) returns the block; {value} in title is replaced by the value (or the label column's)

Example: When and Repeat

byRegion.sql:

-- Sales per region and month
select 'North' as region, '2026-01' as month, 120 as sales
union all select 'North', '2026-02', 135
union all select 'South', '2026-01', 95
union all select 'South', '2026-02', 102

In report.tsx:

import { Repeat, When } from "~/components/report-kit/block-wrap"

import byRegion from "./byRegion.sql?raw"

type Row = { region: string; month: string; sales: number }

// in defineReport({ … })
page: {
  filterParams: [
    {
      param: "view",
      label: "Show",
      input: "select",
      options: () => [
        { value: "chart", label: "Charts" },
        { value: "table", label: "Table" },
      ],
    },
  ],
},
queries: { byRegion: query<Row>(byRegion) },
blocks: (k) => [
  <When key="charts" when={{ input: "view", is: "chart" }}>
    <Repeat
      series="byRegion"
      by="region"
      title="Sales in {value}"
      draw={(title) => (
        <k.Line series="byRegion" x="month" xFormat="month" y={{ sales: "Sales" }} title={title as string} />
      )}
    />
  </When>,
  <When key="table" when={{ input: "view", is: "table" }}>
    <k.Table
      series="byRegion"
      title="Sales by region"
      columns={[
        { key: "region", label: "Region", format: "text" },
        { key: "month", label: "Month", format: "month" },
        { key: "sales", label: "Sales" },
      ]}
    />
  </When>,
],

Formats

A format prints a number. Numeric formats may start with + to print a sign on a positive value too (+number prints "+15 000"), for a change rather than a level.

FormatPrintsExample
number (the default)A whole number with spaces between thousands15 000
countA whole number from 100 up, one decimal below12,5 · 1 250
percentA value already in percent (0–100), one decimal12.3 %
percent0The same, no decimals12 %
decimalA decimal, one by default3,5
millionsMillions with an M4,2M
in_millionsMillions without a unit; put it in suffix45,2 M kr
multipleA multiple2,5×
monthA YYYY-MM key as a monthMar 2026
dateA YYYY-MM-DD date3 Mar 2026
textThe value as it isNorth

Where a format is allowed, prefix, suffix and decimals usually are too. A missing value prints as "–". A percent expects 12.3 for 12.3 %, not 0.123: multiply by 100 in the SQL.

Checks

A prop a block does not take, a missing required one or a wrong type fails the typecheck, as does a series that is not one of the report's queries or a column its rows do not have.