DataTable

A data table bound to a collection in the data model. Columns are declared; rows live in data and update without re-sending the component.

rendered, live
the wire — click a line to scrub
the contract — DataTable in catalog.json
{
  "type": "object",
  "description": "A data table bound to a collection in the data model. Columns are declared; rows live in data and update without re-sending the component.",
  "properties": {
    "component": {
      "const": "DataTable"
    },
    "columns": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "Property name to read from each row object."
          },
          "label": {
            "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString",
            "description": "Column header. Required — it is the column's accessible name."
          },
          "align": {
            "type": "string",
            "enum": [
              "start",
              "center",
              "end"
            ],
            "default": "start",
            "description": "Logical alignment (RTL-safe). Numbers usually read best with 'end'."
          },
          "format": {
            "type": "string",
            "enum": [
              "text",
              "number",
              "datetime"
            ],
            "default": "text",
            "description": "How to interpret cell values: 'number' formats in the user's locale with tabular figures; 'datetime' expects an ISO 8601 string and renders it in the user's locale."
          },
          "sortable": {
            "type": "boolean",
            "default": false,
            "description": "Enables client-side sorting on this column. No round trip."
          }
        },
        "required": [
          "key",
          "label"
        ],
        "additionalProperties": false
      },
      "description": "Flat column descriptors; each names a key present in the row objects."
    },
    "rows": {
      "oneOf": [
        {
          "type": "array",
          "items": {
            "type": "object"
          }
        },
        {
          "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DataBinding"
        }
      ],
      "description": "The row objects. Bind with {\"path\": ...} so rows and individual cells can update via updateDataModel."
    },
    "label": {
      "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString",
      "description": "Optional table caption, e.g. \"Today's deploys\"."
    },
    "emptyText": {
      "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString",
      "description": "Shown when rows resolves to an empty array. Keep it quiet and factual."
    },
    "rowAction": {
      "$ref": "https://a2ui.org/specification/v1_0/common_types.json#/$defs/Action",
      "description": "Fired when the user activates a row. The renderer adds `row` (the row object) and `rowIndex` to the action context automatically."
    },
    "footer": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "Column key this aggregate is computed over and rendered under."
          },
          "aggregate": {
            "enum": [
              "sum",
              "mean",
              "count"
            ],
            "description": "Computed client-side from the resolved rows and recomputed on every updateDataModel — filter the rows and the total follows."
          },
          "label": {
            "type": "string",
            "description": "Optional caption rendered with the value, e.g. 'Total ARR at risk'."
          }
        },
        "required": [
          "key",
          "aggregate"
        ],
        "additionalProperties": false
      },
      "description": "Aggregate footer row. Values are computed client-side from the resolved rows — never emit a precomputed total; it can silently disagree with the rows it sits under."
    }
  },
  "required": [
    "component",
    "columns",
    "rows"
  ]
}
the prompt-pack — paste into your agent
### DataTable — rows bound to data

Columns are declared on the component; rows live in the data model and update without re-sending
the component — including single cells (`"path": "/deploys/1/status"`).

| prop        | type                | required | notes                                                                                                                                     |
| ----------- | ------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `columns`   | array of columns    | yes      | see column shape below                                                                                                                    |
| `rows`      | array \| `{"path"}` | yes      | bind it — rows are data, not components                                                                                                   |
| `label`     | string              | no       | table caption, e.g. `"Today's deploys"`                                                                                                   |
| `emptyText` | string              | no       | shown when rows is empty; quiet and factual                                                                                               |
| `rowAction` | action              | no       | fired on row activation; `row` + `rowIndex` are added to its context automatically                                                        |
| `footer`    | array of aggregates | no       | totals row, **computed client-side from the rows** — never emit a precomputed total; it can silently disagree with the rows it sits under |

Footer cell shape — `key` (a column key) and `aggregate` (`sum | mean | count`) required;
`label` optional. Aggregates recompute on every `updateDataModel` — filter the rows, the total
follows:

```
{"key":"arr","aggregate":"sum","label":"Total ARR at risk"}
```

Column shape — `key` and `label` required:

```
{"key":"durationSec","label":"Duration","align":"end","format":"number","sortable":true}
```

`align`: `start | center | end` (logical, RTL-safe; numbers read best `end`). `format`:
`text | number | datetime` — `datetime` expects an ISO 8601 string and renders in the user's
locale, so emit `"2026-08-16T14:02:11Z"`, never `"2:02 PM"`.

```
{"id":"deploys","component":"DataTable","label":"Today's deploys","columns":[{"key":"service","label":"Service"},{"key":"status","label":"Status"},{"key":"startedAt","label":"Started","format":"datetime"}],"rows":{"path":"/deploys"},"emptyText":"No deploys yet today.","rowAction":{"event":{"name":"view_deploy"}}}
```