Skip to main content
An answer about revenue should be a chart, not a paragraph of numbers. Rich UI components let an agent answer with a chart, a table, KPI cards, code, an image or a link card, rendered natively by the chat widget instead of approximated in markdown.

The eleven components

Each component is a tool. The agent calls it with a complete payload, and the widget renders the result. Table columns carry a format — text, number, currency, percent, date, delta, status, boolean, link, badge or array — plus a locale, so amounts and dates render correctly without the model formatting them as strings. Percent formats declare whether the source value is a fraction (0.12) or a unit (12), because guessing is wrong by a factor of a hundred half the time. A status column is coloured pills, and the colour comes for free: each distinct cell value gets a tone from its meaning, in English or French (confirmed/paid/done → green, pending/partial/requested → amber, cancelled/refused/failed/overdue → red, new/sent/scheduled → blue, unknown states grey). The agent only sends { "kind": "status" }; an optional statusMap of { "<cell value>": { tone, label } } overrides a tone or relabels a value, and anything it leaves out is still coloured. Keys are matched loosely (case, accents, _/- and spacing don’t matter). This needs an SDK that resolves the tone itself; older SDKs get the filled map in the tool result. Any column can be coloured by condition. highlight.rules is a short list of { when: { op, value, field? }, tone } — thresholds and targets: conversion under 50 % red, under 70 % amber, the rest green; field tests another column of the same row, so a hotel name can be tinted by its pickup. highlight.scale tints a numeric column by value instead, sequential (darker as it grows: occupancy, rooms sold) or diverging (danger below a midpoint, success above: pickup against last year). showValue: false hides the text when the colour is the information, as in an availability grid of hotels by stay week (open, min-stay, sold out), the value staying in the tooltip and the export. rowHighlight on the table tints whole rows (overdue, at risk), and layout: "grid" draws a matrix with equal narrow columns and a frozen first column. Tones are the platform’s five, never a CSS colour, and the legend is drawn from the rules and scales automatically. A calendar takes a flat list of events — a title and a start is enough, an ISO datetime with its offset or a YYYY-MM-DD for an all-day entry — and infers the rest: the view from the span of the dates, the day to open on, one hour or one day when there is no end. groupBy: "staff" turns it into a rota with a row per person (a timeline in day view, a grid in week view, the scheduled hours on each row); colorBy colours the entries by any field with a legend; any extra field on an event shows in its popover. For figures per day rather than entries — a rate, an occupancy — the agent sends days instead: a value, a detail, a stacked bar out of a maximum and a badge, rendered as one card per day. Today and the current time come from the viewer’s clock. The same provider also ships quickchart-tool, which returns a chart as an image URL for surfaces that cannot render a live component. Components only display; they never fetch. showWeather is the clearest case: the platform’s weather-tool (provider weather) returns current conditions and a forecast as data in exactly the payload shape showWeather takes, so an agent that should show the weather carries both — it fetches, then calls showWeather with the result. The same fetch-then-render split applies to every data source and every component.

They are tools

Components live under the platform provider rich-ui-components and are assigned per agent like any other tool:
An agent carries only what it needs. A billing agent probably wants tables and stats; a support agent that never shows data needs none. Fewer tools means better tool selection, and the components count. The components an agent has are always loaded directly: they are in the prompt from the first step, never put behind tool search, and never counted against its threshold.

Component anchors

An agent does not emit a component where the tool call happens to fall. It gathers data, registers each component, then writes one continuous answer with a marker placing each visual exactly where it belongs:
The sequence the agent follows:
1

Call the rendering tool with the complete payload

showChart with id: "q3-bookings" and every data point. The tool call is the registration — there is no other way to create a component.
2

Wait for the result

A successful result means the component now exists. An error result means the payload was rejected, with the reason named, and the agent fixes it and calls again.
3

Write the answer, placing each tag where it belongs

<component id="q3-bookings"/> on its own line — double quotes, self-closing, each id at most once. The tag is replaced by the rendered component.
Anchors replaced the older behaviour where a component appeared wherever the tool call fell in the stream, which made the narrative arrive in fragments around the visuals. With anchors the answer reads as one piece of prose with the components inline.

The failure to know about

The tag is a pointer to a component created by a tool call in the same response. A tag whose id has no successful tool call behind it — the model did the placement half and skipped the registration — renders as a visible “Failed to show component” error. The agent is instructed never to write a tag before its call has succeeded, and if it slips, to make the call before the response ends. When you see that error in the wild, the fix is almost always a model that is too small for the number of tools it holds, or a prompt that asks it to “describe the chart” in a way that invites prose instead of a call. Two more rules the agent follows:
  • Only the first payload for an id renders. Calling the tool again with the same id in one response is refused with an error saying so; corrected content needs a new id.
  • Ids do not carry across turns. To show a component again later, the agent calls the tool again in that response and anchors the new call.

Making agents actually use them

An agent that holds components is told which rendering tools it has and when to reach for them: tabular data, trends, KPIs, code, diffs, images and links go through the matching tool rather than markdown. It is also told that a component only exists through a real tool call — never a payload pasted as JSON, never <element> or {{id}} placeholders, never “see the chart below”. You do not need to repeat any of this. What is worth writing in your own prompt is domain guidance: “occupancy questions get a line chart by month; rate comparisons get a table with one row per room type.” Tell the agent which shape suits which question, and let the platform handle the mechanics. The last two model steps of a turn are reserved: on the second-to-last step only rendering tools remain callable, and on the last step none are, so an agent that has been fetching data is pushed to render and answer rather than run out of steps mid-table.

Sizing

Tables default to their natural height (maxHeight is empty); set a CSS height on the payload when a long table should scroll instead of growing. Columns can be marked hideOnMobile or truncate, and given a priority, so a wide table degrades gracefully on a phone. Component payloads are kept in the thread history at a much higher cap than ordinary tool results, so a large table still renders when the conversation is reopened.

In workflows

Every workflow run that posts to a result thread ends with an agent that reads the run and writes the result message — for a user workflow the agent that built it, for an organization workflow the built-in result summary — and has every component here at its disposal. It renders a chart, a table, KPI cards or the weather widget when they present the result better than prose, exactly as chat would, with no configuration. Steer it with the definition’s output_summary.instructions, and require components with output_summary.components (for a user workflow, the present step’s instructions and components): the turn is forced to call each named rendering tool before it answers, and every component it registers is anchored in the text. A workflow can also render a component deterministically: add a rich-UI step fed from earlier steps, and place <component id="<stepId>"/> in a fixed output_text — or leave it to the summary, which can place step components too. See User workflows.