Sortable List

Drag to reorder a list, emitting the new position as one event. A DATA atom, not a domain one: it renders whatever {id, label} pairs it is given and emits a position, so the same atom serves form fields, a shopping run, or any other AUTHORED ordering. There is deliberately no sortable_jobs and no sortable_fields — one named interaction per use case is the drift this vocabulary already has (onFlightClick, onAssign), and it costs a renderer change every time. The reason it fits a declarative binding model at all is containment: the gesture never reaches the state engine. dragstart/dragover/drop are high-frequency and purely visual; only the DROP publishes, once, through the `onReorder` output wire as {id, order}. That is the same shape the atom renders FROM, so position is derived state rather than a second source of truth — what it emits and what it consumes are one field. `order` is fractional: a drop takes the midpoint of its two neighbours, so exactly one item changes. Dense re-indexing would rewrite every position on every drag, turning a one-line reviewable diff into a wall of noise, which matters wherever the result is a change someone accepts or reverts. Use it only where order is AUTHORED. Where order is COMPUTED — a job list ranked by severity then priority — a dragged position becomes a second, competing ordering, and the derivation boundary rule says that ranking belongs to the server.

Surfaces
google-apps-script-webmcp-apps
Degraded on
SurfaceNote
webRenders as a static list; the Python renderer has no drag binding.
Fields
FieldType
itemsarray (required). Objects of {id, label, order?, group?, note?}. `id` is what comes back in the event, `label` is what is shown, `order` seeds the fractional position (defaults to the array index), `group` is an opaque bucket id carried back on drop, `note` is optional right-aligned secondary text.optional
labelstring. A small heading above the list.optional
emptyMessagestring. Shown instead of the list when items is empty. Default "Nothing to reorder."optional
Example payload
{
  "type": "sortable_list"
}
ARD catalog entry
{
  "identifier": "urn:air:a2uicatalog.ai:atom:sortable_list",
  "displayName": "Sortable List",
  "type": "application/vnd.a2ui.atom+json",
  "url": "https://a2uicatalog.ai/atoms/sortable_list",
  "capabilities": [
    "google-apps-script-web",
    "mcp-apps"
  ],
  "description": "Drag to reorder a list, emitting the new position as one event.\nA DATA atom, not a domain one: it renders whatever {id, label} pairs it is given and emits a position, so the same atom serves form fields, a shopping run, or any other AUTHORED ordering. There is deliberately no sortable_jobs and no sortable_fields \u2014 one named interaction per use case is the drift this vocabulary already has (onFlightClick, onAssign), and it costs a renderer change every time.\nThe reason it fits a declarative binding model at all is containment: the gesture never reaches the state engine. dragstart/dragover/drop are high-frequency and purely visual; only the DROP publishes, once, through the `onReorder` output wire as {id, order}. That is the same shape the atom renders FROM, so position is derived state rather than a second source of truth \u2014 what it emits and what it consumes are one field.\n`order` is fractional: a drop takes the midpoint of its two neighbours, so exactly one item changes. Dense re-indexing would rewrite every position on every drag, turning a one-line reviewable diff into a wall of noise, which matters wherever the result is a change someone accepts or reverts.\nUse it only where order is AUTHORED. Where order is COMPUTED \u2014 a job list ranked by severity then priority \u2014 a dragged position becomes a second, competing ordering, and the derivation boundary rule says that ranking belongs to the server.",
  "representativeQueries": [
    "show a drag-to-reorder list emitting {id, order} on drop",
    "drag to reorder a list, emitting the new position as one event.\na data atom, not a domain one: it re",
    "render a sortable list"
  ]
}
← Full ARD catalog
Deploy your own Google Apps Script renderer

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 →