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 providerrich-ui-components and are
assigned per agent like any other tool:
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: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.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’soutput_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.