Skip to main content
View as Markdown

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
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
app.yaml
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: button

itemTemplate 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.

Built with Sovrium