Component catalog
The required field is type. id is only needed on field. Unknown fields are
ignored. Below: the minimal JSON for each view + variants.
table and chart are documented separately: table, chart.
section — group
The only container. Heading, footer, nested components.
{
"type": "section",
"header": "Overview",
"footer": "Updated just now",
"items": [ … ]
}
| Field | Type | Description |
|---|---|---|
header | string? | heading |
footer | string? | footer caption |
items | [component] | contents |
columns | int? | ≥2 → tiles in a grid (e.g. metric) |
template + data | object + string/array | list section — see Data binding |
Tiles in a grid: "columns": 2 + several metric in items.
text — text
{
"type": "text",
"body": "Sync complete",
"style": "caption"
}
style: body (default), headline, caption, footnote.
row — key–value row
Icon + title + value on the right. With an action it’s tappable and leads
further.
{
"type": "row",
"icon": "person",
"title": "Jane Doe",
"subtitle": "admin",
"value": "online",
"action": {
"type": "navigate",
"endpoint": "/pages/users/42"
}
}
| Field | Type | Description |
|---|---|---|
title | string | title |
subtitle | string? | second line |
value | string? | value on the right |
icon | string? | SF Symbol |
status | enum? | badge role instead of value (values — see status) |
action | action? | present → row is tappable |
wrap | bool? | true — wrap, otherwise truncate |
context_menu | [action]? | long-press menu |
swipe_actions | [action]? | swipes (iOS); on Mac → context menu |
Status row: instead of value, specify status — a semantic glyph appears
on the right (success → checkmark, error → cross).
metric — KPI indicator
{
"type": "metric",
"title": "Revenue",
"value": "$340,000",
"trend": "+12%",
"trend_direction": "up",
"sentiment": "positive"
}
| Field | Type | Values |
|---|---|---|
title | string | caption |
value | string | formatted by the server |
trend | string? | ”+12%“ |
trend_direction | enum? | up, down, flat |
sentiment | enum? | positive, negative, neutral — trend color |
Dashboard of tiles: several metric in a section with columns: 2.
status — state badge
{
"type": "status",
"label": "Database",
"role": "success"
}
role: success, warning, error, info, neutral. Each role gets a
semantic color that’s correct in dark mode.
button — button
{
"type": "button",
"title": "Restart",
"icon": "arrow.clockwise",
"role": "destructive",
"action": {
"type": "submit",
"endpoint": "/restart",
"method": "POST",
"confirm": {
"title": "Restart the service?",
"destructive": true
}
}
}
role: primary (accent), secondary (default), destructive (red).
destructive without confirm is
invalid — always pair it with a confirm.field — form field
The only stateful component. id is required — it’s the key in submit.
{
"type": "field",
"id": "email",
"kind": "text",
"label": "E-mail",
"format": "email",
"validation": {
"required": true,
"max_length": 120
}
}
| Field | Type | Values |
|---|---|---|
id | string | key in submit |
kind | enum | text, textarea, number, toggle, picker, date, password |
label | string | caption |
value | any? | initial value |
options | [{value, label}]? | for picker |
validation | object? | required, min, max, max_length |
format | enum? | plain, url, domain, email, username, phone |
persist | bool? | store locally; binding {settings.<id>} |
format sets the keyboard and autocapitalization (domain → no capital first
letter). Dropdown: kind: picker + options. Password: kind: password.
image — content image
{
"type": "image",
"url": "https://cdn.example.com/a.jpg",
"shape": "avatar"
}
shape: avatar (circle), thumbnail, banner. For content only — interface
icons are always SF Symbols.
empty_state — empty state
{
"type": "empty_state",
"icon": "tray",
"title": "No orders",
"message": "New ones will appear here",
"action": {
"type": "navigate",
"endpoint": "/pages/orders/new"
}
}
Fields: title, message?, icon? (SF Symbol), action? (button).