KPI Cards
The kpi component — a single summary metric, with an optional comparison, sparkline and thresholds.
A KPI card states one number. It aggregates the records its dataSource binds and formats the result.
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
label |
string | Descriptive text displayed above the KPI metric value | ||
icon |
string | Lucide icon name displayed alongside the KPI metric (e.g., dollar-sign) |
dataSource
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
dataSource |
object | Data binding for the KPI: a DB table (aggregated client-side) OR a system read endpoint (pre-computed scalar value-path) | ||
dataSource.table |
string | Table to bind to: a declared name (validated against app.tables), or a $param.<name> route reference declared by the page path | ||
dataSource.fields |
array | Specific fields to fetch from the table | ||
dataSource.fields[] |
string | One field name, spelled as the bound table declares it | ||
dataSource.mode |
enum | list, single, search |
Data fetching mode: 'list' (multiple), 'single' (one record), 'search' (interactive) | |
dataSource.filter |
array | Filter conditions applied with AND logic | ||
dataSource.filter[] |
object (truncated) | Single filter condition for data source queries | ||
dataSource.sort |
array | Sort rules applied in order | ||
dataSource.sort[] |
object (truncated) | Single sort rule for data source queries | ||
dataSource.pagination |
object | Pagination configuration for data source | ||
dataSource.pagination.pageSize |
number | Number of records per page | ||
dataSource.pagination.style |
enum | numbered, loadMore, infinite |
How pagination controls are displayed (default: numbered). infinite is accepted but not implemented and pages as numbered. |
|
dataSource.param |
string | Route parameter name for single mode (e.g., slug, id) | ||
dataSource.searchEngine |
enum | client, fts, trigram, hybrid |
Search backend for this data source (default: 'client'). Only 'client' is dispatched today; the other three validate and search as 'client' does. | |
dataSource.searchFields |
array | Fields to search across in search mode | ||
dataSource.searchFields[] |
string | One field name the search term is matched against | ||
dataSource.debounceMs |
number | Debounce delay for search input (ms) | ||
dataSource.limit |
number | Maximum number of results to return | ||
dataSource.targetId |
string | Publisher-side identifier for cross-component references — addressable by a FilterAction (targetDataSource) and by a sibling subscriber's bindTo (shared filter/period state) | ||
dataSource.bindTo |
string | ID of a publisher component whose value drives this data source (cross-component binding). By default a search-input whose query string drives the search; when sharedFilter is also set, a shared filter/period selector whose published params are merged into every request |
||
dataSource.sharedFilter |
object | Companion to bindTo: the bound publisher is a shared filter/period selector whose published params are merged into every request this data source issues. One selector can drive many sibling subscribers. Inert without bindTo. | ||
dataSource.sharedFilter.params |
array (truncated) | Request-param keys this subscriber consumes from the shared publisher's value bag (omit to merge the full bag verbatim) | ||
dataSource.refreshMode |
enum | none, poll, realtime |
Data refresh strategy for this binding (default: 'none'). 'poll' uses pollIntervalMs; 'realtime' subscribes to live change events. | |
dataSource.pollIntervalMs |
number | 30000 | Polling interval in milliseconds for refreshMode: poll (min 1000, max 300000). Defaults to 30000 when omitted, and is ignored unless refreshMode is 'poll'. | |
dataSource.system |
object | Read-endpoint binding: read a single pre-computed scalar (or a value template) from a system endpoint instead of aggregating a DB table | ||
dataSource.system.endpoint |
string | Read endpoint path to fetch the scalar from (e.g. /api/admin/overview) | ||
dataSource.system.valuePath |
string | Dotted path to a single scalar in the fetched envelope | ||
dataSource.system.valueTemplate |
string | Template interpolating {dotted.path} tokens from the fetched envelope | ||
dataSource.system.query |
object | Static query params merged into the request to the endpoint |
autoSave
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
autoSave |
object | Configuration for automatic persistence of edits. Applies to table, form, kanban, and calendar components. | ||
autoSave.saveMode |
enum | auto, onBlur, manual |
Save trigger strategy: 'auto' (debounced), 'onBlur' (field blur), 'manual' (button). Default: 'manual'. | |
autoSave.autoSaveDebounceMs |
number | Debounce delay for auto-save in milliseconds (default: 500, min: 100) | ||
autoSave.showSaveIndicator |
boolean | Display a save status indicator (Saving... / Saved / Error). Default: true when saveMode is auto or onBlur. | ||
autoSave.saveIndicatorPosition |
enum | inline, toast, toolbar |
Where the save status indicator appears |
search
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
search |
object | Shared search bar configuration for data-bound components (table, kanban, calendar) | ||
search.enabled |
boolean | Enable search bar (default: true) | ||
search.placeholder |
string | Search input placeholder text | ||
search.debounceMs |
number | Debounce delay for search input in ms (default: 300) | ||
search.highlight |
boolean | Highlight matched search terms in results (default: false) |
kpiAggregate
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
kpiAggregate |
object | Aggregate function configuration for KPI metric computation | ||
kpiAggregate.function |
enum | count, sum, avg, min, max |
Aggregate function applied to compute the KPI metric value | |
kpiAggregate.field |
string | Field to aggregate (omit for count) |
trend
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
trend |
object | Trend comparison showing change direction and percentage from a previous period | ||
trend.comparisonPeriod |
enum | previousDay, previousWeek, previousMonth, previousQuarter, previousYear |
Time period to compare the current metric value against | |
trend.direction |
enum | up, down, flat |
Direction of change compared to the previous period | |
trend.changePercent |
number | Percentage change from the comparison period | ||
trend.color |
enum | green, red, yellow, gray |
Color indicating whether the trend is positive, negative, or neutral |
kpiFormat
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
kpiFormat |
object | Display formatting configuration for the KPI metric value | ||
kpiFormat.type |
enum | number, currency, percentage, compact, bytes |
Display format for the KPI metric value | |
kpiFormat.options |
object | Additional format options (e.g., { currency: "USD" }) |
thresholds
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
thresholds |
array | Conditional color thresholds for KPI value | ||
thresholds[] |
object | Conditional color threshold — applied when the metric value meets or exceeds the boundary | ||
thresholds[].value |
number | Threshold boundary value | ||
thresholds[].color |
enum | red, green, yellow, blue, gray |
Colour applied when the metric value meets this boundary |
sparkline
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
sparkline |
object | Mini line chart showing recent trend for the KPI metric | ||
sparkline.field |
string | Field to plot in the sparkline | ||
sparkline.groupBy |
string | Date field for grouping data points | ||
sparkline.interval |
enum | day, week, month |
Date grouping interval for sparkline data points | |
sparkline.days |
number | Number of trailing days of data to show |
label is the metric's name and icon draws beside the value. kpiAggregate is { function, field } — function is required, and field is omitted for count. kpiFormat is { type, options }, where type is number, currency, percentage, compact or bytes. thresholds are { value, color } entries that recolour the card once a value is crossed, over red, green, yellow, blue and gray.
tables:
- name: orders
fields:
- { name: total, type: currency, currency: EUR }
- { name: created_at, type: created-at }
pages:
- name: Revenue
path: /revenue
components:
- type: kpi
label: Revenue this month
icon: banknote
dataSource: { table: orders }
kpiAggregate: { function: sum, field: total }
kpiFormat: { type: currency, options: { currency: EUR } }
trend: { comparisonPeriod: previousMonth, direction: up, changePercent: 12.4, color: green }
sparkline: { field: total, groupBy: created_at, interval: day, days: 30 }trend
All three of comparisonPeriod, direction and changePercent are required once trend is present. comparisonPeriod is previousDay, previousWeek, previousMonth, previousQuarter or previousYear; direction is up, down or flat; changePercent is the change as a number; and color is green, red, yellow or gray.
direction and color are independent on purpose. Revenue up is green; churn up is red. Sovrium will not guess which of your metrics improve by rising, so state the colour you mean.
sparkline
All four properties are required once sparkline is present: field is plotted along the line, groupBy buckets the points, interval is day, week or month, and days is how far back the line covers.
Behaviour
KPI Card
type: 'kpi'is a valid component type in page componentsdataSourcewithaggregate(count/sum/avg/min/max) computes metric valuelabeldisplays descriptive text above the metric valueformat.type(number/currency/percentage/compact) controls display formaticonrenders a Lucide icon name alongside the metrictrendshows comparison arrow (up/down), percentage change, and color- A metric takes a threshold's colour only once it meets that boundary
- A threshold colour outside the drawable vocabulary is refused
- User can complete full KPI card workflow (regression)
KPI Grid Layout
- Multiple KPI cards render in a responsive grid layout
- Grid column count adapts per breakpoint (1 mobile, 2-4 desktop)
sparklinerenders a mini line chart showing recent trend for the metricthresholdsapply conditional color to the metric value based on thresholds- A
sparklineon a system binding is refused outright rather than drawn empty - User can complete full KPI grid workflow (regression)
KPI on Public Pages (Anonymous Visitors)
- KPI
labelis server-rendered as visible text in the SSR HTML (not only insidedata-island-props) - Anonymous visitor on a public page sees the label and a graceful value; no
KpiErrorregion replaces it - User can complete the full public/anonymous KPI workflow (regression)
Last updated September 23, 2026
This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta. Contributions and corrections are welcome.