Shared Component Modules
The nine cross-cutting property modules every component type composes from — what each one means, which types carry it, and where its options are listed.
Component types do not each invent their own vocabulary. A small set of shared modules is spread into the types that opt into them, so visibility means the same thing on a button as on a kanban, and learning it once is enough.
- type: table
props: { className: 'rounded-lg' }
dataSource: { table: invoices }
visibility: { roles: [admin, finance] }
responsive: { md: { props: { className: 'text-sm' } } }| Module | Present on | What it does |
|---|---|---|
props |
Every component | Key-value bag rendered as HTML attributes — className, variant, and component-specific keys. |
children |
Container components | Nested component definitions, or plain strings. Arbitrary depth. |
content |
Content and layout | Inline text or markdown, with reference substitution. |
dataSource |
Sixteen data-bound types | Binds to a table or to a system read endpoint. Listed per type, not subtracted — see below. |
visibility |
Most components | Whether the component is rendered at all — by session, role, capability, record or URL state. |
responsive |
Most components | Per-breakpoint property overrides. |
interactions |
Interactive components | Click, hover, scroll and entrance behaviour. |
action |
Form and button components | What running the component does — crud, auth, navigate, automation, fetch. |
i18n |
Content components | Per-language content variants. |
props and children are the two universals. The rest are opt-in, and a type's own reference page is where you find out which of them it accepts.
Seven of these modules never appear in a per-type option table: props and children together, content, visibility, responsive, interactions, action and i18n. A table lists what the type declares for itself, and those are subtracted before it is drawn. Printing them would add roughly a hundred and ninety rows to every one of the ninety types and bury the handful that are actually about that type — button has thirteen options of its own and two hundred and six once the modules are counted in.
dataSource is the exception, and deliberately so. It is not subtracted, so it appears in full in the own table of each of the sixteen types that accept one — calendar, chart, container, drawer, form, gallery, graph, kanban, kpi, list, matrix, record-field, record-picker, select, table and timeline. Those types do not all mean the same thing by it — a kpi reads one aggregate where a table reads a page of rows — so the per-type description is worth the repetition, and the section below covers only what they have in common.
props
An open bag. A key may hold a string, a number, a boolean, an object or an array, and a key present with no value is refused rather than ignored. Because it is open, it has no option table: what a given key means is decided by the type that reads it, and is documented on that type's page.
className is the one key every type reads the same way — Tailwind classes appended after the component's own prestyle, so they win the cascade.
content
Inline text, or a structured object for the types that take one. A string is the common case; an object is how a type carries several named slots ({ button: { text, animation } }). Values resolve the reference families below.
visibility
Whether the component is rendered at all. Every gate is evaluated server-side: a component that fails one is omitted from the HTML entirely rather than hidden with CSS, so its content never reaches a reader who should not have it.
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
when |
enum | authenticated, unauthenticated |
Show component only when user is 'authenticated' or 'unauthenticated' | |
capability |
enum | admin-console, administer-accounts |
A power the requesting session must hold for the component to be rendered at all | |
declares |
enum | auth, auth.apiKeys, auth.twoFactor, auth.groups, tables, forms, links, automations, agents, buckets, connections, analytics |
A capability of the host app — a feature its own config declares — named by a page requirement or by a component visibility gate | |
unlessDeclares |
enum | auth, auth.apiKeys, auth.twoFactor, auth.groups, tables, forms, links, automations, agents, buckets, connections, analytics |
A capability of the host app — a feature its own config declares — named by a page requirement or by a component visibility gate | |
runtime |
enum | ai |
A capability the host app both declares and can actually RUN on this deployment — declared in its config and supported by the environment the process is in | |
unlessRuntime |
enum | ai |
A capability the host app both declares and can actually RUN on this deployment — declared in its config and supported by the environment the process is in |
roles
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
roles |
array | Show component only to users with one of these roles | ||
roles[] |
string | One role name that may see the component |
condition
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
condition |
object | Field-based condition for SSR-excluded visibility | ||
condition.field |
string | Field reference to evaluate (e.g., $user.plan) | ||
condition.operator |
enum | eq, neq |
Comparison operator: eq (equals) or neq (not equals) | |
condition.value |
string | Value to compare the field against |
record
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
record |
object | Per-row gate: render only on records whose field value satisfies the operator(s). Evaluated server-side at row expansion; a failing row omits the element entirely. Requires a record-binding ancestor. |
||
record.field |
string | Record field whose value the visibility predicate is matched against | ||
record.eq |
string | number | boolean | Matches when the value is equal to this one. | ||
record.neq |
string | number | boolean | Matches when the value is different from this one. | ||
record.in |
array | Matches when the value is one of the listed values. | ||
record.in[] |
string | number | boolean | A value the field is compared against — a string, a number or a boolean. | ||
record.notIn |
array | Matches when the value is none of the listed values. | ||
record.notIn[] |
string | number | boolean | A value the field is compared against — a string, a number or a boolean. | ||
record.contains |
string | number | boolean | Matches when the value contains this text, or this entry for a list value. | ||
record.gt |
string | number | boolean | Matches when the value is greater than this one. | ||
record.lt |
string | number | boolean | Matches when the value is less than this one. | ||
record.gte |
string | number | boolean | Matches when the value is greater than or equal to this one. | ||
record.lte |
string | number | boolean | Matches when the value is less than or equal to this one. |
query
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
query |
object | URL-state gate: render only when the named page.query property resolves to a value satisfying the operator(s). Evaluated server-side; a failing gate omits the component and its subtree entirely. |
||
query.name |
string | Declared page.query property whose resolved value the visibility predicate is matched against | ||
query.eq |
string | number | boolean | Matches when the value is equal to this one. | ||
query.neq |
string | number | boolean | Matches when the value is different from this one. | ||
query.in |
array | Matches when the value is one of the listed values. | ||
query.in[] |
string | number | boolean | A value the field is compared against — a string, a number or a boolean. | ||
query.notIn |
array | Matches when the value is none of the listed values. | ||
query.notIn[] |
string | number | boolean | A value the field is compared against — a string, a number or a boolean. | ||
query.contains |
string | number | boolean | Matches when the value contains this text, or this entry for a list value. | ||
query.gt |
string | number | boolean | Matches when the value is greater than this one. | ||
query.lt |
string | number | boolean | Matches when the value is less than this one. | ||
query.gte |
string | number | boolean | Matches when the value is greater than or equal to this one. | ||
query.lte |
string | number | boolean | Matches when the value is less than or equal to this one. |
The unless… keys are the negations of their siblings — unlessDeclares renders only where the app does NOT declare the capability, unlessRuntime only where it cannot run here. They exist so that an alternating body can carry both halves in one page: the catalogue where automations are declared, the honest empty state where they are not. Naming the same capability in both halves is refused when the config is decoded, because the component could then render nowhere.
declares asks what the app being served declares; runtime asks whether that declaration can actually run on this deployment. On a host declaring an agent with no AI provider configured, declares: agents renders and runtime: ai does not.
dataSource
Binds a component to rows. Either a database table or a system read endpoint — the full option list, the filter and sort vocabulary and the reference families live in Data Binding.
The two data-bound extras that ride with it are documented here, because no other page owns them.
autoSave
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
saveMode |
enum | auto, onBlur, manual |
Save trigger strategy: 'auto' (debounced), 'onBlur' (field blur), 'manual' (button). Default: 'manual'. | |
autoSaveDebounceMs |
number | Debounce delay for auto-save in milliseconds (default: 500, min: 100) | ||
showSaveIndicator |
boolean | Display a save status indicator (Saving... / Saved / Error). Default: true when saveMode is auto or onBlur. | ||
saveIndicatorPosition |
enum | inline, toast, toolbar |
Where the save status indicator appears |
search
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
enabled |
boolean | Enable search bar (default: true) | ||
placeholder |
string | Search input placeholder text | ||
debounceMs |
number | Debounce delay for search input in ms (default: 300) | ||
highlight |
boolean | Highlight matched search terms in results (default: false) |
responsive, interactions and action
responsive overrides properties per breakpoint; interactions declares click, hover, scroll and entrance behaviour; action declares what running a control does. All three are large enough to have their own reference pages — see Responsive Design, Interactions, and the action families under Interactivity.
i18n
Per-language variants of a component's content. An open map keyed by language code, so it carries no option table: the keys are the languages the app declares.
Reference substitution
content and props values resolve four reference families at render time:
$record.<field>— the bound record, on a component with a record-binding ancestor.$vars.<key>— page variables.$currentUser.<path>— session context.$t:<key>— a translation key from the app's language files.
A page rendered from markdown adds $frontmatter.*. What each one resolves to, and what happens when it resolves to nothing, is in Data Binding.
Behaviour
Per-row component visibility
- A link inside a list row renders only on the rows whose record satisfies the predicate
- A hidden element is OMITTED from the row, not hidden with CSS
- The gate reaches any depth of a row template, and spends the shared operator vocabulary
- The gate is refused at boot outside a row context, and when it names no operator
- A row whose every child is gated away renders no row wrapper, while a row with a survivor still renders exactly one
- User can complete the full per-row visibility 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.