Galleries
The gallery component — a responsive card grid over records, in a uniform grid, a masonry wall or a carousel.
A gallery draws one card per record. It binds through the shared dataSource module and has no pagination key of its own: filtering, sorting and paging all belong to the binding.
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
layout |
enum | grid, masonry, carousel |
Gallery layout mode: grid | masonry | carousel. carousel lays the same cards on one horizontal track paged by its own controls, so a set too wide to grid is still walkable. |
|
emptyMessage |
string | Message displayed when no records match the data source query |
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) |
gridColumns
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
gridColumns |
object | Responsive column counts per breakpoint for the gallery grid layout | ||
gridColumns.mobile |
number | Number of columns on mobile (default: 1) | ||
gridColumns.sm |
number | Number of columns on small screens (>= 640px) | ||
gridColumns.md |
number | Number of columns on medium screens (>= 768px) | ||
gridColumns.lg |
number | Number of columns on large screens (>= 1024px) | ||
gridColumns.xl |
number | Number of columns on extra-large screens (>= 1280px) |
galleryCard
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
galleryCard |
object | Template configuration for how records render as gallery cards | ||
galleryCard.coverImage |
string | Image URL or $record.* variable for card cover image | ||
galleryCard.aspectRatio |
string | Cover image aspect ratio (e.g. 4:3, 16:9, 1:1) | ||
galleryCard.children |
array | Child component definitions for the card body | ||
galleryCard.children[] |
object | One child component definition, rendered inside the card | ||
galleryCard.onClick |
object | Component action. Discriminated by type: auth (authentication), crud (data operations), automation (invoke workflow), filter (cross-component filtering), navigate (pure URL navigation), toast (transient notification), fetch (client-side HTTP with toast response). Open-drawer is discriminated by action: openDrawer instead of type (PG-04 quick-edit drawer pattern). |
||
galleryCard.onClick.type |
enum | auth, crud, automation, filter, navigate, toast, fetch |
Which kind of action this is. It decides which of the other keys apply. | |
galleryCard.onClick.method |
enum | login, signup, logout, resetPassword, setNewPassword, verifyEmail, GET, POST, PUT, PATCH, DELETE |
What the action performs: the authentication operation under type: auth, or the HTTP verb under type: fetch (default GET). |
|
galleryCard.onClick.strategy |
enum | email, magicLink, oauth |
Authentication strategy to use | |
galleryCard.onClick.provider |
string | OAuth provider name (e.g., google, github) | ||
galleryCard.onClick.submitLabel |
string | Submit-button label for the form this action embeds. Defaults to the localized built-in label for the method or operation. Supports key translation references. | ||
galleryCard.onClick.pendingLabel |
string | Custom in-flight (pending) submit-button label for the auth form. Defaults to the localized built-in pending label for the method. Supports key translation references. | ||
galleryCard.onClick.fields |
array (truncated) | Per-field label/placeholder overrides for the form this action embeds. Targets fields by name. Additive — the default fields are used when omitted. | ||
galleryCard.onClick.onSuccess |
object (truncated) | |||
galleryCard.onClick.onError |
object (truncated) | |||
galleryCard.onClick.operation |
enum | create, update, delete |
Data operation to perform | |
galleryCard.onClick.table |
string | Table to perform the operation on | ||
galleryCard.onClick.confirm |
boolean | If true, shows a confirmation prompt before executing the action | ||
galleryCard.onClick.confirmMessage |
string | Custom confirmation message. Defaults to a generic confirmation prompt. | ||
galleryCard.onClick.data |
object | Field values to apply in bulk update operations | ||
galleryCard.onClick.name |
string | Automation name (must match an automation defined in app.automations) | ||
galleryCard.onClick.inputData |
object | Key-value pairs passed to the automation as input. Supports $variable references. | ||
galleryCard.onClick.await |
boolean | Wait for completion before triggering response (default: false = fire-and-forget) | ||
galleryCard.onClick.targetDataSource |
string | ID of the data source to filter (matches dataSource.targetId) | ||
galleryCard.onClick.field |
string | Field name to apply the filter to | ||
galleryCard.onClick.operator |
enum | eq, neq, contains, gt, lt, gte, lte |
Comparison operator (defaults to eq) | |
galleryCard.onClick.path |
string | Destination URL path (supports $record.X substitution) | ||
galleryCard.onClick.message |
string | Toast notification message. Supports $variable references. | ||
galleryCard.onClick.variant |
enum | success, error, warning, info |
Visual style of the toast notification | |
galleryCard.onClick.duration |
number | Auto-dismiss duration in milliseconds (default: 5000) | ||
galleryCard.onClick.url |
string | Target URL (any absolute path or fully-qualified URL; not prefix-restricted). e.g. /api/tables/contacts/records, /api/auth/admin/ban-user, /api/buckets/default/files/<key> | ||
galleryCard.onClick.mode |
enum | fetch, navigate, download, oauth |
Dispatch mode: fetch (default, client fetch + toast), navigate (browser navigation, e.g. ?format=csv export), download (native file download), oauth (authorize → provider → callback round-trip) | |
galleryCard.onClick.headers |
object | Request headers. Content-Type defaults to application/json when body is set. | ||
galleryCard.onClick.body |
object | JSON request body (serialized with JSON.stringify). String values support $record.<field> and the $session.<field> token (resolved client-side from the caller session, e.g. { confirm: "$session.email" }). | ||
galleryCard.onClick.filename |
string | Suggested download filename. Only meaningful when mode is "download". | ||
galleryCard.onClick.redirectKey |
string | Response field holding the OAuth provider redirect URL (default "url"). Only meaningful when mode is "oauth". | ||
galleryCard.onClick.callbackPath |
string | OAuth provider return path (the registered redirect_uri). Only meaningful when mode is "oauth". | ||
galleryCard.onClick.responseEnvelope |
enum | sovrium, better-auth, raw |
sovrium | Response-envelope interpretation: sovrium (default), better-auth (always-200 enumeration-safe envelope at /api/auth/admin/*), raw (status-only, no body assumptions) |
galleryCard.onClick.action |
enum | openDrawer |
Set to openDrawer to open a sibling drawer instead of running one of the type actions. |
|
galleryCard.onClick.component |
string | ID of the drawer page-component to open (matches a sibling { type: 'drawer', id }) |
||
galleryCard.onClick.props |
object (truncated) | Per-trigger overrides applied to the referenced drawer component | ||
galleryCard.hoverOverlay |
object | Overlay content displayed on card hover | ||
galleryCard.hoverOverlay.children |
array (truncated) | Components rendered in the hover overlay |
gridColumns sets the column count per breakpoint — { mobile, sm, md, lg, xl }, each 1 to 6. layout is grid for uniform rows, masonry for variable heights, or carousel for one walkable track.
tables:
- name: products
fields:
- { name: name, type: single-line-text }
- { name: photo, type: single-attachment }
pages:
- name: Products
path: /products
components:
- type: gallery
dataSource: { table: products }
gridColumns: { mobile: 1, md: 2, lg: 3 }
layout: masonry
galleryCard:
coverImage: '$record.photo'
aspectRatio: '4:3'
children:
- { type: text, element: h3, content: '$record.name' }The card
galleryCard is where a card's own shape is declared. coverImage is usually a $record.* reference and aspectRatio takes a ratio such as 4:3, 16:9 or 1:1. children are the components rendered in the card body, and hoverOverlay: { children } renders over the card on hover. onClick is the action a click on the card runs.
Because children is an ordinary component tree, a card is composed from the same types a page is: a text for the title, a badge for a status, a description-list for a run of facts. $record.<field> resolves inside all of them.
Behaviour
Data Gallery
type: 'gallery'is a valid component type in page componentsdataSource.tablebinds the gallery to a tablecard.childrensupports component children with$record.*varsgridColumnsconfigures responsive grid columns per breakpointcard.coverImagerenders a cover image from a record fieldcard.onClicktriggers a navigation or modal action on card clicklayout: 'grid'renders equal-height cardslayout: 'masonry'renders variable-height cardsdataSource.paginationcontrols pagination stylecard.aspectRatiosets the cover image aspect ratiocard.hoverOverlayrenders action buttons on card hoverlayout: carousellays the same cards on one walkable track, keyboard-reachablestyle: numberedrenders a numbered pager reaching every recordstyle: infinitepages asnumbereddoes, losing no record- An omitted
paginationdraws every record and no pager - User can complete full gallery workflow (regression)
System read-endpoint data source
- A system-source gallery whose
galleryCardfields are NOT app.tables fields boots successfully (field cross-validation is skipped) - With
dataSource.system, the gallery fetchessystem.endpointand never falls through to/api/tables/:t/records - The
{ items: [...] }envelope is normalized to gallery cards;totalfalls back to rows length whentotalKeyis absent - The
galleryCardtemplate renders against endpoint rows (config drives rendering, not a table schema) dataSource.system.querystatic params are merged into the endpoint request- A system-source gallery offers no record-write affordances (no create/edit/delete)
- A system-source gallery offers no saved/user views and no user-preferences (read-only, endpoint-shaped)
- Pagination (
loadMore/numbered/infinite) stays ON for a system source — a read-side control that re-reads the endpoint, never the records API - The existing
dataSource.tablebinding still renders table rows as cards unchanged (discriminated-union regression) - A system-source gallery validates, fetches its endpoint, normalizes the envelope into cards, gates writes off, keeps pagination, leaves DB intact
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.