Skip to main content
Generative UI allows agents to dynamically generate and render UI components in your application, creating rich interactive experiences. The agent can trigger rendering of components like charts, forms, or cards based on the conversation context. AG-Kit provides these primary ways to implement Generative UI:
  1. Client Tools - Define the whole process of client-side tools, including name, parameters, handler and how to render the running process of client-side tools.
  2. Server Tools - Define how to render the running process of server-side tools.
  3. Human-in-the-Loop Interrupts - Define the resume logic and how to render the interrupt when the agent pauses and requests user input.
For details on these features, see Client Side Tools and Human-in-the-Loop. This guide focuses on building and rendering the dynamic UI components.

Concept of Generative UI

In Generative UI, the agent doesn’t generate raw JSX (for security reasons). Instead, it triggers predefined rendering logic on the client by calling tools or sending interrupts with structured payloads. The client then uses this data to render React components dynamically. Key benefits:
  • Dynamic interfaces based on agent logic
  • Interactive elements like forms and charts

Component Registration

useChat provides easy ways to define rendering logic.

Registration for Tools (Client Tools & Server Tools)

For client-side tools, use clientTool with render (for display-only) or renderAndWaitForResponse (if needing to send result back). They receive {input} (and submitToolResult for the latter) and return JSX. Simple Example (display-only):
import { clientTool } from '@ag-kit/ui-react';
import { z } from 'zod';

const myTool = clientTool({
  name: 'show_info',
  description: 'Show info card',
  parameters: z.object({ text: z.string() }),
  render: ({ input }) => <div>{input.text}</div>
});

// Pass to useChat: tools: [myTool]
For server-side tools, use serverTool with render. They receive {input} and return JSX. Simple Example:
import { serverTool } from '@ag-kit/ui-react';
import { z } from 'zod';

const myTool = serverTool({
  name: 'show_info',
  parameters: z.object({ text: z.string() }),
  render: ({ input }) => <div>{input.text}</div>
});

Registration for Interrupts (Human-in-the-Loop)

Use this when the agent needs to pause and get user input via a custom UI. API: In useChat options, provide interrupt.renderWithResume function. It receives {interrupt, resume} and returns JSX. Call resume(payload) to send data back. Simple Example:
import { useChat } from '@ag-kit/ui-react';

useChat({
  interrupt: {
    renderWithResume({ interrupt, resume }) {
      return (
        <button onClick={() => resume('User confirmed')}>
          Confirm
        </button>
      );
    }
  }
});

Rendering Generative UI

If you’ve registered rendering logic as above, the UI will appear automatically in your message stream. In your message rendering code, for parts of type ‘tool-call’ or ‘interrupt’, simply call part.render?.() to display the component. Example:
import { useChat } from '@ag-kit/ui-react';

const { uiMessages } = useChat(/* omitted options */);

{uiMessages.map((m, i) => (
  <div key={i}>
    {m.parts.map((p, j) => {
      if (p.type === 'text') return <p>{p.text}</p>;
      if (p.render) return <div key={j}>{p.render()}</div>;
      // Handle other types
    })}
  </div>
))}
That’s it - the useChat hook handles the rest.

Example: Rendering a Chart with Client Tools

Define a tool that renders a chart:
// Assuming you have a Chart component
import { clientTool } from '@ag-kit/ui-react';
import { Chart } from '@/components/Chart'; // Your chart component
import { z } from 'zod';

const renderChart = clientTool({
  name: 'render_chart',
  description: 'Render a dynamic chart',
  parameters: z.object({
    title: z.string(),
    type: z.enum(['bar', 'line', 'pie']),
    data: z.array(z.object({
      label: z.string(),
      value: z.number()
    }))
  }),
  renderAndWaitForResponse: ({ input, submitToolResult }) => (
    <div>
      <h3>{input.title}</h3>
      <Chart type={input.type} data={input.data} />
      <button 
        onClick={() => submitToolResult('Chart viewed')}
      >
        Close
      </button>
    </div>
  ),
});
In your chat component:
import { useChat } from '@ag-kit/ui-react';

const { uiMessages } = useChat(/* omitted options */);

{uiMessages.map((m, i) => (
  <div key={i}>
    {m.parts.map((p, j) => {
      if (p.type === 'tool-call' && p.render) {
        return <div key={j}>{p.render()}</div>;
      }
      // Other part types...
    })}
  </div>
))}
Pass to useChat: clientTools: [renderChart]

Example: Rendering a Server Tool

Define a server tool that get recommended shopping malls in a city.
import { serverTool } from '@ag-kit/ui-react';
import { z } from 'zod';

const getRecommendedShoppingMalls = serverTool({
  name: 'get_recommended_shopping_malls',
  parameters: z.object({ city: z.string() }),
  render: ({ input }) => <div>{input.city}</div>
});

Example: Rendering a Form with Interrupts

Define interrupt handler that renders a dynamic form:
// In useChat options
interrupt: {
  renderWithResume({ interrupt, resume }) {
    const { fields, title } = interrupt.payload; // Assume payload: { title: string, fields: Array&lt;{name: string, type: string}&gt; }
    
    const [formData, setFormData] = useState({});
    
    const handleSubmit = () => resume(formData);
    
    return (
      <div className="border p-4 rounded-lg">
        <h3>{title}</h3>
        {fields.map(field => (
          <div key={field.name}>
            <label>{field.name}</label>
            <input 
              type={field.type} 
              onChange={e => setFormData(d => ({...d, [field.name]: e.target.value}))}
              className="border p-2 w-full"
            />
          </div>
        ))}
        <button 
          onClick={handleSubmit}
          className="mt-2 px-4 py-2 bg-green-500 text-white rounded"
        >
          Submit
        </button>
      </div>
    );
  },
},