How to Implement Generative UI with Transient Chat Components in Agent-Native
Generative UI in Agent-Native enables agents to render sandboxed, interactive widgets directly within chat transcripts using the render-inline-extension action, creating temporary UI elements that exist only for the current conversation turn.
Generative UI transforms static chat into dynamic experiences by allowing agents to create Alpine.js widgets on demand. In the BuilderIO/agent-native framework, transient chat components provide temporary, sandboxed interfaces for tasks like threshold tuning or quick calculations, automatically rendering inline without persisting to the Extensions panel.
Transient vs. Persisted UI Components
Understanding the lifecycle distinction helps you choose the right implementation path.
Transient components are created using the render-inline-extension tool defined in packages/core/src/extensions/actions.ts. These UI elements appear inline within the chat transcript and automatically disappear when the conversation moves forward. They never create entries in the Extensions list, making them ideal for one-off interactions like sliders, calculators, or temporary data visualizations.
Persisted components use the create-extension action instead, saving the UI to the Extensions view where users can reopen them later with show-extension-inline. Reserve this approach for tools the user needs to access repeatedly.
When you only need a quick interactive control for the current turn, always choose the transient path to keep the interface clean.
Architecture Overview
The generative UI system operates through four coordinated layers:
-
Agent actions: The
render-inline-extensiontool (lines 60–130 inpackages/core/src/extensions/actions.ts) exposes parameters includingname,content, and optionalcontext, returning a payload withchatUI.rendererset to"core.inline-extension". -
Extension sandbox: The chat client mounts your HTML in an iframe with
sandbox="allow-scripts"(strictly omittingallow-same-origin). This isolation prevents the generated UI from accessing host DOM, cookies, or local storage while allowing execution of Alpine.js logic. -
Bridge helpers: The sandbox exposes
window.slotContextfor receiving initial data,agentNative.ui.outputfor writing temporary state, andagentNative.chat.sendfor injecting messages back into the conversation flow. -
Chat renderer: When the client detects
chatUI.renderer === "core.inline-extension", it automatically creates the iframe viaInlineExtensionFrame.tsxand injects the application's Tailwind CSS theme variables.
How to Implement a Transient Component
Follow these steps to create a self-contained, interactive widget that renders inline.
Step 1: Configure the Agent Action
In your agent configuration, call the render-inline-extension tool with a self-contained HTML snippet:
{
"tool": "render-inline-extension",
"arguments": {
"name": "Threshold tuner",
"description": "Adjust the model threshold",
"content": "<div x-data='thresholdTuner'><label class='text-sm font-medium text-foreground'>Threshold</label><input type='range' min='0' max='100' x-model.number='threshold' class='w-full' /><p class='text-sm text-muted-foreground' x-text='`Current: ${threshold}`'></p></div><script>Alpine.data('thresholdTuner',()=>({threshold:50,init(){this.threshold=Number(window.slotContext?.threshold??50);window.onSlotContext?.(ctx=>{if(ctx.threshold!==undefined) this.threshold=Number(ctx.threshold);});}}));</script>"
}
}
The action generates a unique extension ID and returns metadata that triggers inline rendering.
Step 2: Build the Alpine.js UI Snippet
Your content field must contain valid HTML with embedded Alpine.js and Tailwind classes. Access initial context through window.slotContext and listen for updates via window.onSlotContext:
<div x-data="thresholdTuner">
<label class="text-sm font-medium text-foreground">Threshold</label>
<input type="range"
min="0"
max="100"
x-model.number="threshold"
class="w-full"/>
<p class="text-sm text-muted-foreground"
x-text="`Current: ${threshold}`"></p>
<!-- Passive output – writes to application state -->
<button class="mt-2 rounded-md bg-primary px-3 py-1 text-primary-foreground"
@click="agentNative.ui.output({threshold}, {label:'Threshold'})">
Save value
</button>
<!-- Explicit chat message -->
<button class="mt-2 rounded-md bg-secondary px-3 py-1 text-secondary-foreground"
@click="agentNative.chat.send(`Use threshold ${threshold}`, {context: JSON.stringify({threshold}), submit:true})">
Apply in chat
</button>
</div>
<script>
Alpine.data('thresholdTuner', () => ({
threshold: 50,
init() {
// Load initial context passed by the agent
this.threshold = Number(window.slotContext?.threshold ?? 50);
// React to later updates from the agent
window.onSlotContext?.(ctx => {
if (ctx.threshold !== undefined) this.threshold = Number(ctx.threshold);
});
},
}));
</script>
This pattern ensures the component initializes with agent-provided data and can react to subsequent context changes.
Step 3: Retrieve Output Data
To access values submitted via agentNative.ui.output, the agent reads from a temporary state key following the pattern inline-ui:<extensionId>:output:
{
"tool": "read-app-state",
"arguments": {
"key": "inline-ui:{{extensionId}}:output"
}
}
The read-app-state tool returns the JSON object passed to agentNative.ui.output, allowing the agent to continue processing without requiring the user to type values manually.
Security Model and Sandboxing
Security is enforced by InlineExtensionFrame.tsx through strict iframe sandboxing. The generated UI runs with:
sandbox="allow-scripts"permits JavaScript execution- Absence of
allow-same-originprevents access to host cookies, storage, or DOM - Tailwind CSS variables are injected safely via the frame's style context, not the host document
Only explicitly exposed bridge functions (agentNative.ui.output, agentNative.chat.send, appAction, appFetch) can communicate with the host application, ensuring malicious code cannot exfiltrate sensitive data.
Key Implementation Files
Reference these source files when building generative UI features:
packages/core/src/extensions/actions.ts(lines 60–130): Defines therender-inline-extensiontool parameters and return shapepackages/core/src/client/extensions/InlineExtensionFrame.tsx: React component handling iframe creation and Tailwind injectionpackages/core/docs/content/generative-ui.mdx: Design documentation covering lifetimes and security models.agents/skills/generative-ui/SKILL.md: Agent-facing skill guide for determining when to use transient UIpackages/core/src/extensions/actions.spec.ts: Unit tests verifying the transient UI action'sreadOnlyflag and renderer output
Summary
- Transient components use
render-inline-extensionfor temporary UI that disappears after the current chat turn, while persisted components usecreate-extensionfor permanent Extensions panel entries. - Implementation requires calling the action with a self-contained Alpine.js + Tailwind HTML snippet that reads
window.slotContextfor initialization. - The sandboxed iframe runs at
packages/core/src/client/extensions/InlineExtensionFrame.tsxwith strictallow-scriptsisolation and no same-origin access. - Use
agentNative.ui.outputto write temporary state toinline-ui:<id>:outputandagentNative.chat.sendto post explicit messages back to the transcript. - Retrieve saved values later using the
read-app-statetool with the constructed key pattern.
Frequently Asked Questions
What is the difference between transient and persisted generative UI in Agent-Native?
Transient UI renders inline using render-inline-extension and exists only for the current conversation turn, automatically cleaning up when the chat advances. Persisted UI uses create-extension, saves to the Extensions view, and remains accessible across sessions. Choose transient UI for one-off controls like sliders or calculators, and persisted UI for tools users need to reopen repeatedly.
How do I pass initial data to a transient chat component?
Pass a JSON string via the optional context argument when calling render-inline-extension. Inside your Alpine.js component, access this data through window.slotContext during initialization. For reactive updates, implement window.onSlotContext to handle subsequent context changes from the agent.
Can transient UI components access the host application's local storage or cookies?
No. The iframe uses sandbox="allow-scripts" without allow-same-origin, strictly isolating the generated UI from the host document's cookies, local storage, and DOM. Communication is restricted to the agentNative bridge helpers exposed by the sandbox, preventing data exfiltration while permitting controlled interaction with the chat system.
How do I retrieve values submitted by the user in a transient component?
Use agentNative.ui.output within your component to write data, which stores it under the key inline-ui:<extensionId>:output. Then call the read-app-state tool from your agent with that specific key to retrieve the JSON payload. Alternatively, use agentNative.chat.send to post the value as a visible message in the chat transcript.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →