> ## Documentation Index
> Fetch the complete documentation index at: https://agentheya.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# render_ui

> Emit a Generative UI component spec — cards, tables, stats, forms, and interactive elements — directly in the chat.

# render\_ui

Emits a [json-render.dev](https://json-render.dev) component spec in the chat. The agent calls this when it wants to display rich visual UI — dashboards, scorecards, data tables, action panels — without needing an external API call.

For an overview of all three Generative UI surfaces (this tool, custom tool responses, and inline artifacts), see [Generative UI](/generative-ui).

## Parameters

| Name          | Type    | Required | Description                                                                                                                                  |
| ------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `spec`        | object  | yes      | A json-render.dev component tree. Must have `root`, `elements`, and optionally `state`.                                                      |
| `interactive` | boolean | no       | When `true`, buttons and form inputs in the spec are live. The user's submission arrives on the next turn as a tool result. Default `false`. |

## Spec structure

```json theme={null}
{
  "root": "elementId",
  "elements": {
    "elementId": {
      "type": "card",
      "props": { "title": "...", "subtitle": null },
      "children": ["childId"]
    },
    "childId": { ... }
  },
  "state": { "fieldName": "defaultValue" }
}
```

* **`root`** — the id of the top-level element to render.
* **`elements`** — a flat map of element id → definition. Each element has `type`, `props`, and `children` (array of element ids).
* **`state`** — initial values for any `input` elements that use `{ "$bindState": "/field" }`. Required when `interactive: true` and inputs are present.

See [Generative UI → Component reference](/generative-ui#component-reference) for all available components and props.

## Returns

Static render:

```json theme={null}
{ "action": "render", "interactive": false }
```

Interactive render — returns an `interaction_id`. The user's submitted values arrive on the next turn:

```json theme={null}
{ "status": "submitted", "result": { "values": { "fieldName": "value" } } }
```

On spec validation failure: `{ "error": "...", "details": { "hint": "..." } }`.

***

## Examples

### Static metrics card

```json theme={null}
{
  "spec": {
    "root": "card",
    "elements": {
      "card": {
        "type": "card",
        "props": { "title": "Account overview", "subtitle": null },
        "children": ["stats", "divider", "kv"]
      },
      "stats": {
        "type": "list",
        "props": { "direction": "horizontal", "gap": "lg" },
        "children": ["spent", "remaining", "sessions"]
      },
      "spent": {
        "type": "stat",
        "props": { "label": "Credits used", "value": 1240,
                   "change": null, "changeType": null, "prefix": null, "suffix": null },
        "children": []
      },
      "remaining": {
        "type": "stat",
        "props": { "label": "Credits remaining", "value": 3760,
                   "change": null, "changeType": null, "prefix": null, "suffix": null },
        "children": []
      },
      "sessions": {
        "type": "stat",
        "props": { "label": "Sessions this month", "value": 47,
                   "change": "+12%", "changeType": "up", "prefix": null, "suffix": null },
        "children": []
      },
      "divider": { "type": "divider", "props": {}, "children": [] },
      "kv": {
        "type": "key_value",
        "props": {
          "items": [
            { "key": "Plan",      "value": "Pro" },
            { "key": "Renews",    "value": "2026-06-01" },
            { "key": "API calls", "value": "382 this month" }
          ]
        },
        "children": []
      }
    },
    "state": {}
  }
}
```

***

### Interactive action panel

Set `interactive: true` so the submit button works. The user's selection returns to the agent on the next turn.

```json theme={null}
{
  "interactive": true,
  "spec": {
    "root": "panel",
    "elements": {
      "panel": {
        "type": "card",
        "props": { "title": "Resolve ticket #1042", "subtitle": null },
        "children": ["details", "divider", "form"]
      },
      "details": {
        "type": "key_value",
        "props": {
          "items": [
            { "key": "Customer", "value": "Jane Smith" },
            { "key": "Issue",    "value": "Unable to export CSV" },
            { "key": "Priority", "value": "High" }
          ]
        },
        "children": []
      },
      "divider": { "type": "divider", "props": {}, "children": [] },
      "form": {
        "type": "form",
        "props": { "title": "Resolution", "description": null },
        "children": ["actionSelect", "notesInput", "submitBtn"]
      },
      "actionSelect": {
        "type": "input",
        "props": {
          "kind": "select",
          "name": "action",
          "label": "Action",
          "placeholder": null,
          "required": true,
          "value": { "$bindState": "/action" },
          "options": [
            { "value": "fix_sent",  "label": "Fix sent — mark resolved" },
            { "value": "workaround","label": "Shared workaround — awaiting feedback" },
            { "value": "escalate",  "label": "Escalate to engineering" }
          ],
          "min": null, "max": null
        },
        "children": []
      },
      "notesInput": {
        "type": "input",
        "props": {
          "kind": "textarea",
          "name": "notes",
          "label": "Notes",
          "placeholder": "Optional internal notes...",
          "required": false,
          "value": { "$bindState": "/notes" },
          "options": null, "min": null, "max": null
        },
        "children": []
      },
      "submitBtn": {
        "type": "button",
        "props": { "label": "Submit", "variant": "primary", "disabled": null },
        "on": { "press": { "action": "submit" } },
        "children": []
      }
    },
    "state": { "action": "", "notes": "" }
  }
}
```

***

### Status table

```json theme={null}
{
  "spec": {
    "root": "card",
    "elements": {
      "card": {
        "type": "card",
        "props": { "title": "Integration status", "subtitle": null },
        "children": ["table"]
      },
      "table": {
        "type": "table",
        "props": {
          "caption": null,
          "columns": ["Service", "Status", "Last sync"],
          "rows": [
            ["Slack",          "Connected",    "2 min ago"],
            ["GitHub",         "Connected",    "14 min ago"],
            ["Jira",           "Needs re-auth", "3 days ago"],
            ["Google Calendar","Disconnected", "—"]
          ]
        },
        "children": []
      }
    },
    "state": {}
  }
}
```

***

### Alert with badges

```json theme={null}
{
  "spec": {
    "root": "card",
    "elements": {
      "card": {
        "type": "card",
        "props": { "title": "Deployment summary", "subtitle": "v2.4.1 → production" },
        "children": ["notice", "metrics"]
      },
      "notice": {
        "type": "alert",
        "props": {
          "title": "Deployment succeeded",
          "message": "All 3 services deployed and health checks passed.",
          "variant": "success"
        },
        "children": []
      },
      "metrics": {
        "type": "list",
        "props": { "direction": "horizontal", "gap": "md" },
        "children": ["b1", "b2", "b3"]
      },
      "b1": {
        "type": "badge",
        "props": { "label": "3 services", "variant": "default" },
        "children": []
      },
      "b2": {
        "type": "badge",
        "props": { "label": "0 errors", "variant": "success" },
        "children": []
      },
      "b3": {
        "type": "badge",
        "props": { "label": "12s rollout", "variant": "default" },
        "children": []
      }
    },
    "state": {}
  }
}
```

***

## Call from your own custom tool

```js theme={null}
export async function handle(input, ctx) {
  return await ctx.tools.render_ui({
    interactive: false,
    spec: {
      root: 'card',
      elements: {
        card: {
          type: 'card',
          props: { title: input.title, subtitle: null },
          children: ['kv'],
        },
        kv: {
          type: 'key_value',
          props: {
            items: input.items.map((i) => ({ key: i.k, value: i.v })),
          },
          children: [],
        },
      },
      state: {},
    },
  });
}
```

```python theme={null}
async def handle(input, ctx):
    return await ctx.tools.render_ui({
        'interactive': False,
        'spec': {
            'root': 'card',
            'elements': {
                'card': {
                    'type': 'card',
                    'props': {'title': input['title'], 'subtitle': None},
                    'children': ['kv'],
                },
                'kv': {
                    'type': 'key_value',
                    'props': {
                        'items': [{'key': i['k'], 'value': i['v']} for i in input['items']]
                    },
                    'children': [],
                },
            },
            'state': {},
        },
    })
```

## Notes

* Always registered when `render_ui` is enabled for the agent.
* For interactive renders where the submit action is destructive, pair with `confirm_action` + `commit_action` — `render_ui` does not provide an anti-hallucination gate on its own.
* To include a file download card alongside a visual layout, call `surface_file` first and embed the returned `downloadUrl` in a `key_value` element.
