Generative UI
Render the agent's step rows, cards, checklists, comparisons, charts, maps, and tabs in your own app.
The agent answers in markdown. When a project turns on Generative UI, the agent may add
```openui blocks for structured content: a ranked recommendation, a comparison, a chart from
tool data, places on a map. Short answers, explanations, and code stay prose. A guide, a trip, or a
shortlist reads as short paragraphs with one block per section.
How the agent learns the blocks
The catalog ships as the kortix-genui system skill, not as system-prompt text. With the genui
project flag on, every new session lists the skill by name and a
one-line description. The agent loads the full catalog only when an answer needs a block, so a turn
without UI pays nothing for it. With the flag off, the skill is hidden. An agent with an explicit
skills: list in kortix.yaml sees the skill only when the list names kortix-genui.
Install
Generative UI uses two optional peer dependencies. Install them before you import any
@kortix/sdk/genui entry; without them the import fails:
npm i zod @openuidev/lang-core@0.3.1
@openuidev/lang-core sends one pseudonymous PostHog event at install time. Set
OPENUI_TELEMETRY_DISABLED=1 or DO_NOT_TRACK=1 to turn it off. Runtime telemetry is off unless
you opt in.
| Import | Holds | Loads the peers |
|---|---|---|
@kortix/sdk/genui/fence |
genuiVersionFromClassName, genuiVersionOf, splitGenui: fence detection |
No |
@kortix/sdk/genui |
parseGenui, genuiToMarkdown, genuiBlockToMarkdown, the catalog |
Yes |
@kortix/sdk/genui/react |
GenuiBlock, defaultGenuiComponents |
Yes |
The root @kortix/sdk and @kortix/sdk/react entries never load the peers. Check a fence with
@kortix/sdk/genui/fence in your main bundle, and load the renderer lazily once a block exists.
Render blocks
@kortix/sdk/genui/react exports GenuiBlock, a headless renderer. You pass a component for each
block name, or defaultGenuiComponents for unbranded HTML defaults.
| Prop | Meaning |
|---|---|
code |
The fence body so far |
version |
genuiVersionFromClassName(className) or genuiVersionOf(tag) |
streaming |
This block’s fence is still open while the message streams. Pass false once the fence closes, so a finished block settles while the reply goes on |
components |
Block name → your component (GenuiComponentProps) |
renderMarkdown |
Your markdown renderer, used for fallbacks. Pass a stable function (module constant or useCallback): a new function re-renders the block’s markdown parts and node views |
renderPending |
A node the model has not finished, while the block streams. Default: nothing. Return a fixed-height box for heavy nodes (a chart, a table) so the layout does not jump when the node settles |
enabled |
false renders the block as markdown (a viewer setting) |
onSettled |
One event per block: outcome, component names, timing. Never content |
A Checklist keeps its ticks in the component only: nothing is stored or sent.
Plain text hosts
genuiToMarkdown(text) replaces every block with deterministic markdown. Charts become tables with a
Source: line; maps become place lists; tabs and accordions expand.
Blocks inside blockquotes and list items convert too. For a reply that is still streaming, pass
genuiToMarkdown(text, { streaming: true }): the unfinished last block shows no “cut off” note.
Steps become a numbered list; a Checklist becomes a task list (- [ ]).
A host on an older @kortix/sdk does not know a block added later (Steps and Checklist arrived after
v1). It shows This content could not be displayed. in that block’s place, not the block’s markdown.
Update the SDK to render or convert new blocks.
Blocks
Stack, Card, Stat, StatRow, Table, Compare, RankedList, BarChart, LineChart, PieChart, Map, Steps, Checklist, Tabs, Accordion, Badge, Callout, Image, Link. Limits (counts, lengths) are enforced by the SDK; a block that breaks a limit is dropped and its siblings still render.
Safety
Blocks hold data, never code. Every link and image URL must be absolute http or https. Generated
UI cannot run tools or change state.