A clickable image grid: every tile is both a photo and a row. Built to close a real gap — data_table has no way to show what a row IS, only what it says about itself, so a photo-driven decision (which wallpaper, which chair) forced a text table plus a separate "View photo" link plus a separate select-then-tap-a-button flow before an action could fire. This atom is the shape that removes all three: the thing you look at and the thing you click are the same element. Reuses data_table's onRowClick contract exactly — same data-row-json attribute, same _a2uiBindRowClicks binder, same array-wire form for "select AND immediately run an action" in one tap (shipped 2026-08-04 for onRowClick, inherited here for free rather than rebuilt). A DATA atom, not a domain one, same reasoning sortable_list gives for its own genericness: it renders whatever {id, url, ...} objects it is given and fires whatever the caller wired onRowClick to, so the same atom serves a voting board, a photo picker, or a product chooser without a second named atom per use case. `badge` and `active` exist because a click-driven UI still has to show its OWN state back — a starred tile that does not visibly read as starred after the tap is a button that looks like it did nothing. Both are plain strings/booleans the caller derives server-side (a star count, a "you picked this" flag); the atom does not compute either.
| Surface | Note |
|---|---|
| web | Renders as a static grid; no Python renderer exists for this atom yet, so tiles are not clickable outside the wired dialect. |
| Field | Type | |
|---|---|---|
| images | array (required). Objects of {id, url, alt?, caption?, badge?, active?}. `id` is what data-row-json carries back to onRowClick (along with every other field on the object — the WHOLE row, same contract data_table's onRowClick already has). `caption` overlays the bottom of the tile. `badge` is a short string pinned top-right (a count, a status) — omit it for no badge. `active` outlines the tile and tints its badge to mark "this one is in a selected/chosen state" from the caller's own point of view. | required |
| cols | integer (optional, default 3). 2-4 reads best; more than 4 on a phone-width viewport crowds captions into illegibility. | optional |
{
"type": "photo_grid",
"images": 1
}
{
"identifier": "urn:air:a2uicatalog.ai:atom:photo_grid",
"displayName": "Photo Grid",
"type": "application/vnd.a2ui.atom+json",
"url": "https://a2uicatalog.ai/atoms/photo_grid",
"capabilities": [
"google-apps-script-web",
"mcp-apps"
],
"description": "A clickable image grid: every tile is both a photo and a row.\nBuilt to close a real gap \u2014 data_table has no way to show what a row IS, only what it says about itself, so a photo-driven decision (which wallpaper, which chair) forced a text table plus a separate \"View photo\" link plus a separate select-then-tap-a-button flow before an action could fire. This atom is the shape that removes all three: the thing you look at and the thing you click are the same element.\nReuses data_table's onRowClick contract exactly \u2014 same data-row-json attribute, same _a2uiBindRowClicks binder, same array-wire form for \"select AND immediately run an action\" in one tap (shipped 2026-08-04 for onRowClick, inherited here for free rather than rebuilt). A DATA atom, not a domain one, same reasoning sortable_list gives for its own genericness: it renders whatever {id, url, ...} objects it is given and fires whatever the caller wired onRowClick to, so the same atom serves a voting board, a photo picker, or a product chooser without a second named atom per use case.\n`badge` and `active` exist because a click-driven UI still has to show its OWN state back \u2014 a starred tile that does not visibly read as starred after the tap is a button that looks like it did nothing. Both are plain strings/booleans the caller derives server-side (a star count, a \"you picked this\" flag); the atom does not compute either.",
"representativeQueries": [
"show a clickable image grid \u2014 data-row-json tiles, reuses data_table's onRowClick contract; badge + active show per-tile state",
"a clickable image grid: every tile is both a photo and a row.\nbuilt to close a real gap \u2014 data_table",
"render a photo grid"
]
}
The renderer is an open-source Google Apps Script web app. Deploy your own instance in 4 commands — you own the URL, no dependency on the demo endpoint.
git clone https://github.com/a2uicatalog/a2ui
cd a2ui/apps-script-surface/gas-wired-renderer
clasp push && clasp deploy
Full deploy guide →