#Widget Catalogue
Widgets are the building blocks of an app dashboard page. Each is one entry in a
page's widgets record — widgetComponent selects the widget, config is its
options (config values may carry {{ … }} smart values, see
Smart Values):
"widgets": {
"tasks": { "widgetComponent": "data_grid", "config": { "header": "Open tasks", "columns": [ … ] } }
}
For the structure that holds widgets (dashboards → pages → widgets) see App spec structure.
#A real app, rendered
Everything below renders with the actual app widgets — this is a live Tasks dashboard (a heading, a task table, and a create-task form), not a screenshot:
{
"title": "Tasks · main dashboard",
"widgets": [
{
"widgetName": "header",
"config": {
"text": "Tasks",
"headerLevel": 1
}
},
{
"widgetName": "table",
"config": {
"header": "Open tasks",
"columns": [
{
"field": "title",
"header": "Task",
"type": "string"
},
{
"field": "state",
"header": "State",
"type": "string"
},
{
"field": "priority",
"header": "Priority",
"type": "number"
}
],
"rows": [
{
"id": "t1",
"title": "Draft kickoff brief",
"state": "doing",
"priority": 1
},
{
"id": "t2",
"title": "Set up CI",
"state": "todo",
"priority": 2
},
{
"id": "t3",
"title": "Write API smoke test",
"state": "done",
"priority": 3
}
]
}
},
{
"widgetName": "form",
"config": {
"header": "New task",
"fields": [
{
"id": "title",
"label": "Task title",
"type": "string",
"required": true,
"placeholder": "What needs doing?"
},
{
"id": "state",
"label": "State",
"type": "string",
"options": [
{
"label": "To do",
"value": "todo"
},
{
"label": "Doing",
"value": "doing"
},
{
"label": "Done",
"value": "done"
}
]
},
{
"id": "priority",
"label": "Priority",
"type": "number",
"min": 1,
"max": 5
}
],
"submit": {
"label": "Add task"
}
}
}
]
}
This page uses the table widget (its rows are inline, so the preview is
self-contained). In a production app the server-resolved data_grid does the same
job at scale — fetching rows from a live dataSource, paginating, and carrying row
actions. The form submits a command and buttons navigate or run actions; previews
here are visual, so interactions are inert.
#The 13 widgets
#header
A static (or smart-valued) heading.
| Field | Type | Req | Notes |
|---|---|---|---|
text |
string | ✓ | Heading text (may be {{ }}). |
headerLevel |
int 1–3 | Visual level; defaults to 1. |
{
"widgetName": "header",
"config": {
"text": "Vessels",
"headerLevel": 2
}
}
#data_grid
The workhorse: a paginated, server-resolved grid with row + bulk actions and
cross-widget filtering. Columns read row.attributes[field].
| Field | Type | Req | Notes |
|---|---|---|---|
dataSource |
dataSource | ✓ | $query / $http / $rows / $typeSchema producer. |
columns |
array | ✓ | { type, field, header } — field reads the entity's attribute. |
actions / leftActions |
rowAction[] | Per-row buttons; each carries a command step-sequence. |
|
selectable |
boolean | Checkbox column; checked ids round-trip as thisWidget.selectedRows. |
|
chips |
object | Filter chips. | |
pageSize / paginate |
number / boolean | Page size + keyset paging toggle. | |
header, noResultsMessage |
string | Copy. |
A data_grid resolves its rows from the live dataSource at render time, so it
populates in a running app (the live preview above uses the inline-row table
widget to stand in for it).
#table
A simpler, client-side table over inline rows (no server fetch / pagination).
Columns read each row flat (row[field]).
| Field | Type | Req | Notes |
|---|---|---|---|
columns |
array | ✓ | { field, header, type } — read flat from each row. |
rows |
array | ✓ | Inline rows (often a {{ }}/$jsonata projection). |
actions / leftActions |
rowAction[] | Per-row buttons (command). |
|
groupBy |
object | Group rows under headings. | |
header, noResultsMessage |
string | Copy. |
{
"widgetName": "table",
"config": {
"header": "Recent",
"columns": [
{
"field": "name",
"header": "Name",
"type": "string"
},
{
"field": "status",
"header": "Status",
"type": "string"
}
],
"rows": [
{
"id": "1",
"name": "Aurora",
"status": "active"
},
{
"id": "2",
"name": "Borealis",
"status": "docked"
}
]
}
}
#form
An input form that submits a command. Fields are string / number / boolean /
array; a select is a string field with static options.
| Field | Type | Req | Notes |
|---|---|---|---|
fields |
array | ✓ | { id, label, type, required?, options?, … }. |
submit |
object | { label } — the primary submit button. |
|
buttons |
array | Additional buttons, each with a command. |
|
initialValues |
record | Seed values per field. | |
inline |
boolean | Inline (vs stacked) layout. |
{
"widgetName": "form",
"config": {
"header": "New task",
"fields": [
{
"id": "title",
"label": "Title",
"type": "string",
"required": true
},
{
"id": "priority",
"label": "Priority",
"type": "number"
}
],
"submit": {
"label": "Create"
}
}
}
#page_tabs
A row of tab buttons that navigate between pages of the dashboard.
| Field | Type | Req | Notes |
|---|---|---|---|
tabs |
array | ✓ | { label, page }[] — each tab navigates to a page key. |
{
"widgetName": "page_tabs",
"config": {
"tabs": [
{
"label": "Overview",
"page": "root"
},
{
"label": "Details",
"page": "detail"
},
{
"label": "Activity",
"page": "activity"
}
]
}
}
#section_switcher
A button group that writes a page.data key; sections gate visibility on it via
$if (no navigation).
| Field | Type | Req | Notes |
|---|---|---|---|
stateKey |
string | ✓ | The page.data key this switcher writes. |
options |
array | ✓ | { label, value }[] — the selectable sections. |
{
"widgetName": "section_switcher",
"config": {
"stateKey": "view",
"options": [
{
"label": "List",
"value": "list"
},
{
"label": "Board",
"value": "board"
}
]
}
}
#breadcrumbs
A breadcrumb trail — explicit crumbs or an entity ancestry chain, with an optional refresh button.
| Field | Type | Req | Notes |
|---|---|---|---|
crumbs |
array | Explicit { label, page } crumbs. |
|
ancestry |
object | { entityType, entityId, ancestors: [{ type, labelAttr }] } — derive from parents. |
|
refresh |
boolean | Append a ↻ button that re-renders the page. |
{
"widgetName": "breadcrumbs",
"config": {
"crumbs": [
{
"label": "Vessels",
"page": {
"id": "vessels"
}
},
{
"label": "MV Northern Star",
"page": {
"id": "vessel",
"params": {
"id": "v-100"
}
}
},
{
"label": "Voyage 2025-0142"
}
],
"refresh": true
}
}
#entity_info
A labelled read-only field list for one entity (a definition-list card).
| Field | Type | Req | Notes |
|---|---|---|---|
entityType |
string | ✓ | Entity type to resolve. |
entityId |
string | ✓ | Entity id (usually {{ page.params.id }}). |
fields |
array | ✓ | { label, attr }[] — label + attribute, in order. |
header |
string | Optional card heading. |
{
"widgetName": "entity_info",
"config": {
"header": "Vessel",
"entityType": "Vessel",
"entityId": "v1",
"fields": [
{
"label": "Name",
"attr": "name"
},
{
"label": "IMO",
"attr": "imo"
},
{
"label": "Flag",
"attr": "flag"
}
]
},
"build": {
"instance": {
"id": "v1",
"attributes": {
"name": "Aurora",
"imo": "9876543",
"flag": "Panama"
}
}
}
}
#related_collections
Lists an entity's related collections as grouped sections — config-driven, so adding a collection is a config-only change.
| Field | Type | Req | Notes |
|---|---|---|---|
entityId |
string | ✓ | The detail entity id. |
collections |
array | ✓ | { type, via }[] — related type + relationship rule. |
collapsible |
boolean | Render each section collapsible. | |
header |
string | Optional heading. |
The preview supplies sample rows that the engine would normally resolve from each collection's relationship rule:
{
"widgetName": "related_collections",
"config": {
"entityId": "voyage-1",
"header": "Related",
"collections": [
{
"label": "Tasks",
"type": "Task",
"via": "Voyage",
"columns": [
{
"field": "title",
"header": "Task"
},
{
"field": "status",
"header": "Status",
"type": "tag"
},
{
"field": "due",
"header": "Due",
"type": "date"
}
]
},
{
"label": "Alerts",
"type": "Alert",
"via": "Voyage",
"columns": [
{
"field": "summary",
"header": "Alert"
},
{
"field": "severity",
"header": "Severity",
"type": "tag"
}
],
"noResultsMessage": "No alerts"
}
]
},
"build": {
"relatedCollections": [
{
"rows": [
{
"title": "Submit port docs",
"status": "open",
"due": "2025-09-10"
},
{
"title": "Confirm berth",
"status": "done",
"due": "2025-09-08"
}
]
},
{
"rows": [
{
"summary": "Late ETA",
"severity": "high"
}
]
}
]
}
}
#actions
A free-standing button group (not bound to a row), e.g. a page footer bar.
| Field | Type | Req | Notes |
|---|---|---|---|
actions |
array | ✓ | { label, command, icon?, variant? }[] — each runs a step-sequence. |
asFooter |
boolean | Pin to a footer bar. |
{
"widgetName": "actions",
"config": {
"actions": [
{
"label": "Refresh",
"command": []
},
{
"label": "Export CSV",
"command": []
}
]
}
}
#action_bar
A status-catalog action bar for one entity (drives a command per catalog item).
| Field | Type | Req | Notes |
|---|---|---|---|
entityType, entityId |
string | ✓ | The bound entity. |
catalog |
catalogSource | ✓ | Status/item catalog source. |
instances |
instanceSource | ✓ | Per-item instance source. |
invoke |
object | ✓ | { command, label? } — the step-sequence a bar action runs. |
{
"widgetName": "action_bar",
"config": {
"entityType": "Voyage",
"entityId": "voyage-1",
"catalog": {
"bindingType": "VoyageStepBinding",
"boundTypeAttr": "bound_type",
"definitionType": "VoyageStepDefinition",
"definitionRefAttr": "definition_key",
"keyAttr": "key",
"labelAttr": "label"
},
"instances": {
"type": "VoyageStepInstance",
"parentKeyAttr": "voyage_key",
"keyAttr": "step_key",
"stateAttr": "state"
},
"invoke": {
"label": "Initiate {label}",
"command": [
{
"kind": "refresh"
}
]
}
},
"build": {
"process": {
"boundType": "Voyage",
"catalog": [
{
"key": "departure_clearance",
"label": "Departure clearance",
"sortOrder": 0,
"secondary": false,
"hideInStatusTable": false,
"dependsOn": [],
"condition": null,
"enabled": true,
"attrs": {
"key": "departure_clearance"
}
},
{
"key": "pilot_booking",
"label": "Pilot booking",
"sortOrder": 1,
"secondary": true,
"hideInStatusTable": false,
"dependsOn": [],
"condition": null,
"enabled": true,
"attrs": {
"key": "pilot_booking"
}
}
],
"states": {},
"context": {
"entity": {
"metadata": {}
},
"parent": null,
"completed_job_ids": [],
"permissions": [],
"user": {}
},
"parent": {
"id": "voyage-1",
"type": "Voyage",
"key": "2025-0142"
}
}
}
}
#status_checklist
A checklist of catalog items with per-item status + row actions.
| Field | Type | Req | Notes |
|---|---|---|---|
entityType, entityId |
string | ✓ | The bound entity. |
catalog |
catalogSource | ✓ | Item catalog. |
instances |
instanceSource | ✓ | Per-item instances. |
statusMap |
object | Status → display mapping. | |
actions |
array | Per-row actions (command). |
{
"widgetName": "status_checklist",
"config": {
"entityType": "Voyage",
"entityId": "voyage-1",
"header": "Voyage checklist",
"catalog": {
"bindingType": "VoyageStepBinding",
"boundTypeAttr": "bound_type",
"definitionType": "VoyageStepDefinition",
"definitionRefAttr": "definition_key",
"keyAttr": "key",
"labelAttr": "label"
},
"instances": {
"type": "VoyageStepInstance",
"parentKeyAttr": "voyage_key",
"keyAttr": "step_key",
"stateAttr": "state"
},
"statusMap": {
"DONE": {
"emoji": "🟢",
"label": "Done"
},
"READY": {
"emoji": "🔴",
"label": "To do"
}
},
"actions": [
{
"id": "start",
"label": "Start",
"enabledWhen": [
{
"property": "state",
"operator": "equals",
"value": "READY"
}
],
"command": [
{
"kind": "refresh"
}
]
}
]
},
"build": {
"process": {
"boundType": "Voyage",
"catalog": [
{
"key": "departure_clearance",
"label": "Departure clearance",
"sortOrder": 0,
"secondary": false,
"hideInStatusTable": false,
"dependsOn": [],
"condition": null,
"enabled": true,
"attrs": {
"key": "departure_clearance"
}
},
{
"key": "cargo_manifest",
"label": "Cargo manifest",
"sortOrder": 1,
"secondary": false,
"hideInStatusTable": false,
"dependsOn": [],
"condition": null,
"enabled": true,
"attrs": {
"key": "cargo_manifest"
}
}
],
"states": {
"cargo_manifest": {
"state": "DONE",
"stateAt": "2025-09-05T00:00:00Z",
"attrs": {}
}
},
"context": {
"entity": {
"metadata": {}
},
"parent": null,
"completed_job_ids": [
"cargo_manifest"
],
"permissions": [],
"user": {}
},
"parent": {
"id": "voyage-1",
"type": "Voyage",
"key": "2025-0142"
}
}
}
}
#custom_markup
An escape hatch: an authored markup tree with named action handlers.
| Field | Type | Req | Notes |
|---|---|---|---|
markup |
blockElement | ✓ | The markup tree to render. |
actionHandlers |
record | Named handlers → step-sequences invoked from the markup. |
{
"widgetName": "custom_markup",
"config": {
"markup": {
"type": "dataList",
"items": [
{
"label": "Vessel",
"content": "MV Northern Star"
},
{
"label": "Voyage",
"content": "2025-0142"
},
{
"label": "Status",
"content": "In transit"
},
{
"label": "ETA",
"content": "2025-09-14"
}
]
}
}
}
Every widget above renders live on this page. The entity-bound widgets
(entity_info, related_collections, status_checklist, action_bar) are shown
with sample data the engine resolves at render time in a real app. The data_grid
is the one widget that needs a live data source ($query/$http) — it renders its
columns + actions but populates its rows only in a running app; see
Apps (Bridge) for an end-to-end app.