#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:

Loading live app preview…
{
  "title": "Tasks · main dashboard",
  "widgets": [
    {
      "widgetName": "headline",
      "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.

#Content & data widgets

#headline

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.
Loading live widget preview…
{
  "widgetName": "headline",
  "config": {
    "text": "Vessels",
    "headerLevel": 2
  }
}

#data_grid

The workhorse: a paginated, server-resolved grid with row + bulk actions. Columns read the entity's attributes by bare name. Filtering composes from OUTSIDE the grid: a standalone filter_bar (free inputs) or chips (preset filters) widget publishes state that the grid's SOURCE reads via optional filter particles — the grid never references the filter widget.

Field Type Req Notes
dataSource string The NAME of an entry in the page's dataSources map ($query / $http / $rows producer — $typeSchema cannot feed a grid).
columns array { type, field, header?, display?, lookup?, sortable?, filterable?, minWidth?, maxWidth? }type is string / number / date; field is the bare attribute name (or a relationship type name), never a storage path; display is a JSONata expression over value and row producing the cell text; lookup maps raw values to labels; sortable / filterable: false opt a synthesized column out.
actions / leftActions rowAction[] Per-row buttons; each carries a command step-sequence.
selectable boolean Checkbox column; checked ids round-trip as thisWidget.selectedRows.
expand boolean Toggles the open row's id into grid state; a separate author-placed widget reads it to render the detail.
pageSize / paginate number / boolean Page size + keyset paging toggle.
header, noResultsMessage string Copy.

A data_grid resolves its rows from its named source 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).

#process_data_grid

A data_grid for entities that carry a process — a set of steps with per-entity status. Each row gains a status column per step plus an invoke-from-row menu, on top of everything data_grid offers.

Field Type Req Notes
dataSource, columns, actions, selectable, pageSize, … As data_grid. The source's include must carry each row's process instances (default field instances).
process object { entityType, nodes, instancesField?, model }nodes is the source NAME of the step catalog; model is inline ({ catalog, statusMap, instances }) or { fromConfig: { slot, scope } }.
statusColumns array | object Which steps get a column: a static list of step keys paired with author-declared ps_<key> columns, or { fromConfig: { slot, scope, type? } } reading a per-tenant { key, header }[] list that also generates the columns.
invoke object { page } — adds a per-row invoke menu that navigates to that page with the step in scope.
instances object { keyAttr, stateAttr, stateAtAttr?, activeAttr? } — override the model's instance projection when the include shape differs.

Every sourceable value here follows one rule: inline, or { "fromConfig": { "slot", "scope" } } reading config.<scope>.<slot>. Status columns are sortable and filterable on their underlying state. The process bundle assembles from live sources and config, so there is no static preview:

{
  "widgetComponent": "process_data_grid",
  "config": {
    "dataSource": "voyages",
    "columns": [{ "type": "string", "field": "name", "header": "Voyage" }],
    "process": {
      "entityType": "Voyage",
      "nodes": "processNodes",
      "model": {
        "fromConfig": { "slot": "voyage_process", "scope": "company" }
      }
    },
    "statusColumns": {
      "fromConfig": { "slot": "voyage_status_columns", "scope": "company" }
    },
    "invoke": { "page": "voyage_step" }
  }
}

#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.
Loading live widget preview…
{
  "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. In its plain shape it collects values and submits a command; with bindings it edits entities or a principal config directly and generates its own submit.

Field Type Req Notes
fields array The inputs — see the field vocabulary below.
submit object { label, command? } — the primary button. With command, the form's values reach the steps as outputs.form.<id>; without it, a bound form generates its own writes.
buttons array Additional buttons, each with a command.
initialValues record Seed values per field id (wins over a field's default).
inline boolean Inline (vs stacked) layout.
emptyFallback object { message, variant? } shown when every field is gated away.
entities record Bound form — entity slots (below).
config object Bound form — { principalType }: fields derive from that principal-config schema and submit writes the config.
links array Bound form — relationships the submit maintains (below).
onSuccess / onError steps Run after a bound submit succeeds / fails.

Fields. Every field has id, type, and optionally label, placeholder, required, default; a field entry may be $if-gated. The type selects the input:

type Extra keys Renders
string options?, showAsButtons?, showAsDropdown?, numLines?, maxLength?, format? (date) A text input; with options, a select (showAsButtons → a button row, showAsDropdown → an auto-applying dropdown).
number min?, max? A numeric input.
boolean A toggle.
array options (required) A multi-select.
dateRange Two date pickers, published as <id>.from / <id>.to.
secretInput target (required) — { scope, scopeId?, name, env? } A write-only secret input; the value may only flow into a setSecret step. See Secrets.
file previewPage?, accept?, maxFileSize?, buttonLabel?, uploadType? An upload. Empty it renders an Upload button; once uploaded, the host's upload element plus Preview. See below.

options is either a static [{ value, label }] array or { "dataSource": "<name>", "limit"?: n } binding a page source whose rows carry value and label (shape them with a $pipe map step).

The file field is a composite, not a single input. Clicking Upload mints a signed capability for that field's slot and re-renders with the host's upload element in its place; the value it ends up holding is the uploaded temp file's id, which is then ordinary form state — outputs.form.<id> on submit, page.widgets.<form>.state.<id> elsewhere. previewPage names the modal a Preview button opens (no previewPage, no Preview button); accept takes dotted extensions or MIME types; maxFileSize is a per-file byte ceiling capped at 25 MiB. A file field is never bound — its value is a file id that no attribute schema derives — so in is refused where it is authored. The full lifecycle is in Apps → File uploads.

Loading live widget preview…
{
  "widgetName": "form",
  "config": {
    "header": "New task",
    "fields": [
      {
        "id": "title",
        "label": "Title",
        "type": "string",
        "required": true
      },
      {
        "id": "priority",
        "label": "Priority",
        "type": "number"
      },
      {
        "id": "urgent",
        "label": "Urgent",
        "type": "boolean"
      }
    ],
    "submit": {
      "label": "Create"
    }
  }
}

Bound forms. Declaring entities binds the form to one or more entity slots; each slot is { type, id?, matchOn?, seed? } and its mode follows from what it carries — id present ⇒ edit that entity; matchOn present ⇒ upsert by those identifier attributes (prefilled when a match exists, created otherwise); neither ⇒ create. A field binds to a slot with "in": "<slot>", "attribute": "<name>" (or "in": "config", "key": "<name>" for a config binding), and its input type derives from the slot type's schema — the field restates neither. links are the relationships the submit maintains: { rule, from, to, direction, mode? }, where an endpoint is a slot name, a concrete entity id, or { field } (a picker whose value is the target id), direction names the endpoint the carrying slot occupies, and mode: "replace" makes the listed targets the whole set for that rule. submit is required with bindings; the writes (upsertEntity per slot, links attached) are generated for you.

{
  "widgetComponent": "form",
  "config": {
    "entities": {
      "cert": { "type": "VesselCertificate", "seed": { "status": "valid" } }
    },
    "fields": [
      {
        "id": "cert_type",
        "in": "cert",
        "attribute": "type",
        "label": "Certificate"
      },
      {
        "id": "expires",
        "in": "cert",
        "attribute": "expiry_date",
        "label": "Expires"
      }
    ],
    "links": [
      {
        "rule": "VesselToVesselCertificate",
        "from": "{{ page.params.vessel_id }}",
        "to": "cert",
        "direction": "to"
      }
    ],
    "submit": { "label": "Add certificate" },
    "onSuccess": [{ "kind": "closeModal" }, { "kind": "refresh" }]
  }
}

#filter_bar

The free-input filter affordance: a pure STATE PUBLISHER. It renders inputs, round-trips their values as widget state, and does nothing else — the page's SOURCES read page.widgets.<bar>.state.<field> via optional filter particles, and every interaction re-renders in full. No widget references the bar.

Field Type Req Notes
fields array The same field vocabulary as form (string / multiselect / dateRange…); showAsDropdown / showAsButtons apply on CHANGE.
apply object { label } — rendered by default (label "Apply") when any field applies on submit; a bar of only change-to-apply fields renders none.
clear boolean | object A Clear button — a type-aware STATE reset of this bar's fields (strings → "", arrays → []).
inline boolean Row layout (default) vs stacked.
Loading live widget preview…
{
  "widgetName": "filter_bar",
  "config": {
    "fields": [
      {
        "id": "q",
        "label": "Search",
        "type": "string"
      },
      {
        "id": "status",
        "label": "Status",
        "type": "string",
        "options": [
          {
            "value": "open",
            "label": "Open"
          },
          {
            "value": "done",
            "label": "Done"
          }
        ],
        "showAsDropdown": true
      }
    ],
    "clear": true
  }
}

#chips

The preset-filter affordance — one row of exclusive chips publishing the selected value as widget state (the preset face of what filter_bar does with free inputs).

Field Type Req Notes
id string The state key the selected value publishes to.
options array { value, label }[] — one chip each.
default string Initially-selected value.
Loading live widget preview…
{
  "widgetName": "chips",
  "config": {
    "id": "status",
    "options": [
      {
        "value": "all",
        "label": "All"
      },
      {
        "value": "open",
        "label": "Open"
      },
      {
        "value": "done",
        "label": "Done"
      }
    ],
    "default": "open"
  }
}

#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.
Loading live widget preview…
{
  "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.
Loading live widget preview…
{
  "widgetName": "section_switcher",
  "config": {
    "stateKey": "view",
    "options": [
      {
        "label": "List",
        "value": "list"
      },
      {
        "label": "Board",
        "value": "board"
      }
    ]
  }
}

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 { dataSource, root?, detailPage, self } — walk the named detail read's parent chain into crumbs (dataSource is a dataSources entry name).
refresh boolean Append a ↻ button that re-renders the page.
Loading live widget preview…
{
  "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). The entity arrives through a named source — typically a single: true point-read in the page's dataSources map.

Field Type Req Notes
dataSource string Name of the dataSources entry carrying the entity.
fields array { label, attr }[] — label + attribute, in order.
header string Optional card heading.
Loading live widget preview…
{
  "widgetName": "entity_info",
  "config": {
    "header": "Vessel",
    "dataSource": "vessel",
    "fields": [
      {
        "label": "Name",
        "attr": "name"
      },
      {
        "label": "IMO",
        "attr": "imo"
      },
      {
        "label": "Flag",
        "attr": "flag"
      }
    ]
  },
  "data": {
    "vessel": {
      "id": "v1",
      "attributes": {
        "name": "Aurora",
        "imo": "9876543",
        "flag": "Panama"
      }
    }
  }
}

Lists an entity's related collections as grouped sections — a pure PROJECTION of one detail read: the named source is a point-read whose include tree materializes each collection under an as: alias, and every collections entry names that alias via key.

Field Type Req Notes
dataSource string Name of the detail point-read carrying the collections.
collections array { label, key, columns, noResultsMessage? }[]key is the include's as alias on the detail row.
collapsible boolean Render each section collapsible.
header string Optional heading.
Loading live widget preview…
{
  "widgetName": "related_collections",
  "config": {
    "dataSource": "voyageDetail",
    "header": "Related",
    "collections": [
      {
        "label": "Tasks",
        "key": "tasks",
        "columns": [
          {
            "field": "title",
            "header": "Task"
          },
          {
            "field": "status",
            "header": "Status",
            "type": "tag"
          },
          {
            "field": "due",
            "header": "Due",
            "type": "date"
          }
        ]
      },
      {
        "label": "Alerts",
        "key": "alerts",
        "columns": [
          {
            "field": "summary",
            "header": "Alert"
          },
          {
            "field": "severity",
            "header": "Severity",
            "type": "tag"
          }
        ],
        "noResultsMessage": "No alerts"
      }
    ]
  },
  "data": {
    "voyageDetail": {
      "id": "voyage-1",
      "tasks": [
        {
          "attributes": {
            "title": "Submit port docs",
            "status": "open",
            "due": "2025-09-10"
          }
        },
        {
          "attributes": {
            "title": "Confirm berth",
            "status": "done",
            "due": "2025-09-08"
          }
        }
      ],
      "alerts": [
        {
          "attributes": {
            "summary": "Late ETA",
            "severity": "high"
          }
        }
      ]
    }
  }
}

#actions

A free-standing button group (not bound to a row), rendered as a plain row of buttons. For the pinned footer-bar treatment, wrap it in a footer layout widget.

Field Type Req Notes
actions array { label, command, icon?, variant? }[] — each runs a step-sequence.
asFooter boolean Accepted; placement is decided by the layout — pin the bar with a footer node.
Loading live widget preview…
{
  "widgetName": "actions",
  "config": {
    "actions": [
      {
        "label": "Refresh",
        "command": []
      },
      {
        "label": "Export CSV",
        "command": []
      }
    ]
  }
}

#action_bar

An "invoke the actionable steps" button bar for one entity's process: a primary button for the first non-secondary invokable step plus an overflow dropdown for the rest. Shares the process block with status_checklist / process_data_grid; the widget is pure — its nodes / states sources are names in the page's dataSources map.

Field Type Req Notes
process object { entityType, entityId, parentKeyFrom?, nodes, states, model }nodes/states are source NAMES; model is inline or { fromConfig: { slot, scope } }.
invoke object { command, label? } — the step-sequence an invoked step runs; label may carry a {label} placeholder.

The invoked step's node/parent seed outputs for the command ({{ outputs.node.* }} / {{ outputs.parent.* }}). Renders nothing when no step is invokable. Because the process bundle assembles from live sources + config, there is no static preview here — see the process guides for a running example:

{
  "widgetComponent": "action_bar",
  "config": {
    "process": {
      "entityType": "Voyage",
      "entityId": "{{ page.params.id }}",
      "nodes": "processNodes",
      "states": "processStates",
      "model": {
        "fromConfig": { "slot": "voyage_process", "scope": "company" }
      }
    },
    "invoke": { "label": "Start {label}", "command": [{ "kind": "refresh" }] }
  }
}

#status_checklist

A checklist of process steps with per-step status + row actions. Same process block as action_bar.

Field Type Req Notes
process object { entityType, entityId, parentKeyFrom?, nodes, states, model } — as action_bar; the model's statusMap drives status display.
actions array Per-step row actions — { id, label, command, enabledWhen?, reinvokeLabel? }.
header string Optional heading.
noResultsMessage string Copy when the process has no steps.
fullWidth boolean Stretch the checklist to the full column width.
{
  "widgetComponent": "status_checklist",
  "config": {
    "header": "Voyage checklist",
    "process": {
      "entityType": "Voyage",
      "entityId": "{{ page.params.id }}",
      "nodes": "processNodes",
      "states": "processStates",
      "model": {
        "fromConfig": { "slot": "voyage_process", "scope": "company" }
      }
    },
    "actions": [
      { "id": "start", "label": "Start", "command": [{ "kind": "refresh" }] }
    ]
  }
}

#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.
Loading live widget preview…
{
  "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"
        }
      ]
    }
  }
}

#Layout widgets

Layout widgets structure a page rather than showing data. They are placed as inline nodes in a page's layout tree — { "component": "...", "config": {...}, "children": [...] } — instead of entries in the widgets record; the grammar and its publish-time rules are documented in Apps (Bridge) → Page layout. The containers receive their children from the tree; the leaves stand alone.

#section (container)

A titled, optionally collapsible group with a column grid.

Field Type Req Notes
title string Section heading.
columns / columnSpan int Column grid inside / span when nested.
collapsible / defaultCollapsed boolean Collapse behaviour.
justify enum left / center / right.

#row (container)

A horizontal run of leaf elements.

Field Type Req Notes
justify enum left / center / right / between.
columnSpan int Span when nested in a section grid.

#tabs (container, top-level only, requires id)

Tabbed groups — each config.items[i] carries its own children slot.

Field Type Req Notes
items array { id, label, children }[] — one slot per tab.
variant enum inline / segment.
scrollable boolean Scroll overflowing tab strips.

#header (container, top-level only)

The sticky page header: a title plus filter controls. Not the text heading — that's headline. Children must be filter elements (filterButtons / filterDropdown).

Field Type Req Notes
title string Header title.

The pinned bottom bar (the modal action-bar treatment). Children are text and buttons — e.g. wrap an actions widget reference to pin it.

#divider (leaf)

A horizontal rule. No config.

#iframe (leaf)

An embedded external page. src must be https.

Field Type Req Notes
src string https://… URL.
height / width number Pixel dimensions.

#file_viewer (leaf)

An inline document viewer (PDF and other browser-renderable types) served through the platform's signed component URLs — the engine signs the source at render time, so the file URL itself is never exposed to the client unsigned.

Field Type Req Notes
fileUrl string The file to render (often a {{ }} value).
mimeType string The file's MIME type.
fileName string Display name.
startPage number Initial page (paged formats).
height number Viewer height (default 500).

Every content 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. Layout widgets render as part of a page's layout tree rather than standalone, so they are documented without live previews; see Apps (Bridge) for an end-to-end app.