Element-Tree Primitives

When predefined schemas don’t fit, set card_kind: "custom" and provide an element tree as card_payload. The payload root must be { "root": <node> }. Maximum nesting depth is 8 levels (root counts as 1).

Layout primitives

  • {type: "section", title?: str, subtitle?: str, children: [<node>, ...]} — labeled section.
  • {type: "group", direction: "row" | "column", gap?: int, children: [<node>, ...]} — flex container.
  • {type: "divider"} — horizontal rule.

Content primitives

  • {type: "text", value: str, style?: "body" | "muted" | "emphasis"}
  • {type: "heading", value: str, level: 1 | 2 | 3}level defaults to 2.
  • {type: "image", src: str, alt?: str, aspect_ratio?: str} (e.g. "16:9")
  • {type: "badge", label: str, variant?: "info" | "success" | "warning"}

Interactive primitives

  • {type: "button", label: str, primary?: bool, action: {selection: {...}}} — submits the collected selection on click.
  • {type: "input", name: str, kind: "text" | "number" | "email" | "select" | "checkbox", label: str, required?: bool, options?: [{value, label}, ...], placeholder?: str}select requires non-empty options.
  • {type: "list", items: [{children: [<node>, ...], action?: {selection: {...}}}, ...]} — selectable list. Tapping an item with action is equivalent to clicking a button.
  • {type: "choice_grid", name: str, choices: [{value, label, image?}, ...], multi?: bool} — image-tile picker.

How selection works

The drawer collects all input and choice_grid values keyed by their name, then merges them with the clicked button’s action.selection to form the final selection payload.

Example: a form with three fields and a submit button produces:

{
"first_name": "Alex",
"email": "alex@example.com",
"country": "GB",
"action": "submit"
}

Example: hotel picker built from primitives

Consider the following card_payload:

{
"root": {
"type": "section",
"title": "Hotels in London",
"children": [
{
"type": "list",
"items": [
{
"children": [
{"type": "image", "src": "https://example.com/h1.jpg", "aspect_ratio": "16:9"},
{"type": "heading", "value": "The Strand", "level": 3},
{"type": "text", "value": "Covent Garden · 3 nights · USD 540"},
{"type": "badge", "label": "Free cancellation", "variant": "success"}
],
"action": {"selection": {"hotel_id": "h_strand"}}
}
]
}
]
}
}

You should get something similar to: