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:

  1. Agent actions: The render-inline-extension tool (lines 60–130 in packages/core/src/extensions/actions.ts) exposes parameters including name, content, and optional context, returning a payload with chatUI.renderer set to "core.inline-extension".

  2. Extension sandbox: The chat client mounts your HTML in an iframe with sandbox="allow-scripts" (strictly omitting allow-same-origin). This isolation prevents the generated UI from accessing host DOM, cookies, or local storage while allowing execution of Alpine.js logic.

  3. Bridge helpers: The sandbox exposes window.slotContext for receiving initial data, agentNative.ui.output for writing temporary state, and agentNative.chat.send for injecting messages back into the conversation flow.

  4. Chat renderer: When the client detects chatUI.renderer === "core.inline-extension", it automatically creates the iframe via InlineExtensionFrame.tsx and 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-origin prevents 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:

Summary

  • Transient components use render-inline-extension for temporary UI that disappears after the current chat turn, while persisted components use create-extension for permanent Extensions panel entries.
  • Implementation requires calling the action with a self-contained Alpine.js + Tailwind HTML snippet that reads window.slotContext for initialization.
  • The sandboxed iframe runs at packages/core/src/client/extensions/InlineExtensionFrame.tsx with strict allow-scripts isolation and no same-origin access.
  • Use agentNative.ui.output to write temporary state to inline-ui:<id>:output and agentNative.chat.send to post explicit messages back to the transcript.
  • Retrieve saved values later using the read-app-state tool 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →