Cards

The card document as a tree of elements — the six element types, the one that binds to your data, and how rules and overrides layer on top.


A card is one document describing one small page: a tree of elements, in order, with style and handlers attached wherever you want them.

{
  "version": 2,
  "style": { "gap": 6, "borderRadius": 12, "padding": { "x": 14, "y": 10 } },
  "on": { "click": [ { "command": "openPeek" } ] },
  "elements": [
    { "type": "text", "text": "Morning run", "style": { "fontWeight": "semibold" } },
    { "type": "field", "property": "distance_km", "display": "stat" },
    { "type": "group", "style": { "flexDirection": "row", "gap": 8 }, "elements": [
      { "type": "icon", "icon": "lucide:timer" },
      { "type": "field", "property": "elapsed", "control": "stopwatch" }
    ] }
  ]
}

Nothing about that shape is card-specific. The same tree, the same style keys, the same events and commands describe a list row, a dashboard widget or a board — which is why moving a design from one to another is mostly a copy and paste.

The six element types

TypeIsCarries
fielda bound cell — it names one of your propertiesproperty, control, display, options, label, style, on
buttona control whose whole meaning is its handlerlabel, icon, style, on
textwords you wrotetext, style, on
iconan emoji, or an icon by nameicon, style, on
imagea picture you attachedmedia, style, on
groupa nestable boxelements, style, on

field is the only element that binds

Everything else is ornament. text, icon and image show what you put in them; only a field names a real property and shows the value stored on the entry. That distinction is the reason a card can be checked: name a property that no longer exists and the app can tell you, by name, which card is broken.

Ornament is still allowed to act. An icon can carry an on handler and run a command, and that is not a back door — it is the ordinary, recorded command path, the same one a button uses. What ornament cannot do is bind.

control vs display

A field shows a value, and you choose how.

  • control picks the editor — how you change the value in place. input, select, radio, checkbox, stopwatch, map, markdown, and so on. The list available to a field depends on the property's kind: you cannot put a stopwatch on a text property.
  • display picks an alternate presentation — how the value reads when you are not editing it. stat for a big number, hero or cover for an image that fills the card, thumb for a small one, trace for a route, value for the plain reading.

Set neither and the field renders the ordinary way for its kind, which is usually what you want.

Handlers, rules and ids

on may sit at any level, and the nearest handler wins — see Events.

Rules are the conditional layer: an expression decides whether an element shows, or which style it takes, based on the entry's own values. That is where if lives; a handler itself is always a flat list.

id is optional. You only need one on an element that something else has to point at — a rule, or an override.

Overrides, and what wins

The same card can look slightly different in different places, and it does that by layering rather than by forking. Your theme is the bottom layer, so a card that says nothing already looks right. On top of that, more specific layers win: the collection's own card, then a particular view's version of it, then a preset's, then a rule that only applies on some entries, then a narrow-screen adjustment last of all.

The practical consequence is that you should say as little as possible. Every key you set is a key that stops following your theme.

Versions

Every card carries a version, and older cards are upgraded as they load. A card you designed before the language grew a word keeps rendering exactly as it did.

Next