Lists
The list component — a vertical run of records drawn from a per-record template, with Load More paging.
A list draws one entry per record, from a template you declare. Everything about presentation lives under listDisplay; what is fetched, and how much of it, belongs to dataSource.
dataSource
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
dataSource |
object | DB-table binding (DataSource) OR a system read-endpoint binding | ||
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: feed a data-bound component from a system endpoint instead of a DB table | ||
dataSource.system.endpoint |
string | Read endpoint path to fetch rows from (e.g. /api/admin/automations/runs) | ||
dataSource.system.rowsKey |
string | Key of the rows array in the response envelope (default: 'items') | ||
dataSource.system.param |
string | Route parameter substituted into the endpoint's :param placeholder. Must be declared by the host page's path. | ||
dataSource.system.idKey |
string | Key of each row's unique id (default: 'id') | ||
dataSource.system.totalKey |
string | Key of the total-count in the envelope; falls back to rows length if absent | ||
dataSource.system.query |
object | Static query params merged into every request to the endpoint | ||
dataSource.system.bindTo |
string | ID of a sibling shared filter/period selector whose published params are merged into every request to endpoint (the dynamic counterpart to the static query) | ||
dataSource.system.sharedFilter |
object (truncated) | Companion to bindTo: the shared selector params merged (dynamically) into every request to endpoint, alongside the static query. Inert without bindTo. |
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) |
listDisplay
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
listDisplay |
object | Search-first result display configuration for list components with item templates and pagination | ||
listDisplay.itemTemplate |
object | Template for rendering each record as a list item with $record.* variables | ||
listDisplay.itemTemplate.title |
string | Primary text using $record.* variable reference | ||
listDisplay.itemTemplate.subtitle |
string | Secondary text using $record.* variable reference | ||
listDisplay.itemTemplate.image |
string | Image URL using $record.* variable reference | ||
listDisplay.itemTemplate.badge |
string | Badge text using $record.* variable reference | ||
listDisplay.itemTemplate.metadata |
array (truncated) | Metadata fields displayed in the list item footer | ||
listDisplay.emptyMessage |
string | Message when no search results match | ||
listDisplay.loadMore |
enum | button, infinite |
Pagination: 'button' shows a Load More button, 'infinite' loads on scroll | |
listDisplay.highlight |
boolean | Highlight matched search terms in list item text (default: false) | ||
listDisplay.divider |
boolean | Show visual dividers between list items (default: false) | ||
listDisplay.maxItems |
number | Maximum number of items to display |
tables:
- name: articles
fields:
- { name: title, type: single-line-text }
- { name: published_at, type: datetime }
pages:
- name: Articles
path: /articles
components:
- type: list
dataSource:
table: articles
sort: [{ field: published_at, direction: desc }]
limit: 20
listDisplay:
itemTemplate:
title: '$record.title'
metadata: [{ field: published_at, format: relative-date }]
loadMore: buttonitemTemplate takes title, subtitle, image, badge and metadata, where metadata is an array of { field, format } rendered in the item footer.
Paging is the binding's business, not the display's
Page size comes from dataSource.limit: it sets how many records the first page holds, and each press of Load More appends another page of that size.
loadMore: button renders the control that does the appending. On a dataSource.system binding it appears only if that binding also declares totalKey and the endpoint answers a number there — without one the reported total is the page's own length, so there is never anything left to load.
maxItems caps how many records the list DRAWS. It does not change what is fetched — a page is transport, a cap is display — so a list at its cap hides the Load More control, since anything the next page brought would be cut off on arrival. To fetch fewer records, set dataSource.limit instead.
Three keys are accepted and then ignored
Stated here rather than left to be discovered: loadMore: infinite, highlight and divider have no effect. Each decodes cleanly and changes nothing, so a list declaring loadMore: infinite pages exactly like one declaring nothing at all.
They are documented because an unread key is the hardest kind of config to debug: it validates, it renders, and the behaviour it names never arrives. If you need infinite scrolling today, loadMore: button is what exists.
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.