📄 ai-sdk/docs/ai-sdk-ui/generative-user-interfaces

File: generative-user-interfaces.md | Updated: 11/15/2025

Source: https://ai-sdk.dev/docs/ai-sdk-ui/generative-user-interfaces

AI SDK

Menu

v5 (Latest)

AI SDK 5.x

AI SDK by Vercel

AI SDK 6 Beta

Foundations

Overview

Providers and Models

Prompts

Tools

Streaming

Getting Started

Navigating the Library

Next.js App Router

Next.js Pages Router

Svelte

Vue.js (Nuxt)

Node.js

Expo

Agents

Agents

Building Agents

Workflow Patterns

Loop Control

AI SDK Core

Overview

Generating Text

Generating Structured Data

Tool Calling

Model Context Protocol (MCP) Tools

Prompt Engineering

Settings

Embeddings

Image Generation

Transcription

Speech

Language Model Middleware

Provider & Model Management

Error Handling

Testing

Telemetry

AI SDK UI

Overview

Chatbot

Chatbot Message Persistence

Chatbot Resume Streams

Chatbot Tool Usage

Generative User Interfaces

Completion

Object Generation

Streaming Custom Data

Error Handling

Transport

Reading UIMessage Streams

Message Metadata

Stream Protocols

AI SDK RSC

Advanced

Reference

AI SDK Core

AI SDK UI

AI SDK RSC

Stream Helpers

AI SDK Errors

Migration Guides

Troubleshooting

Copy markdown

Generative User Interfaces

======================================================================================================================

Generative user interfaces (generative UI) is the process of allowing a large language model (LLM) to go beyond text and "generate UI". This creates a more engaging and AI-native experience for users.

What is the weather in SF?

getWeather("San Francisco")

Thursday, March 7

47°

sunny

7am

48°

8am

50°

9am

52°

10am

54°

11am

56°

12pm

58°

1pm

60°

Thanks!

At the core of generative UI are tools , which are functions you provide to the model to perform specialized tasks like getting the weather in a location. The model can decide when and how to use these tools based on the context of the conversation.

Generative UI is the process of connecting the results of a tool call to a React component. Here's how it works:

  1. You provide the model with a prompt or conversation history, along with a set of tools.
  2. Based on the context, the model may decide to call a tool.
  3. If a tool is called, it will execute and return data.
  4. This data can then be passed to a React component for rendering.

By passing the tool results to React components, you can create a generative UI experience that's more engaging and adaptive to your needs.

Build a Generative UI Chat Interface


Let's create a chat interface that handles text-based conversations and incorporates dynamic UI elements based on model responses.

Basic Chat Implementation

Start with a basic chat implementation using the useChat hook:

app/page.tsx

'use client';
import { useChat } from '@ai-sdk/react';import { useState } from 'react';
export default function Page() {  const [input, setInput] = useState('');  const { messages, sendMessage } = useChat();
  const handleSubmit = (e: React.FormEvent) => {    e.preventDefault();    sendMessage({ text: input });    setInput('');  };
  return (    <div>      {messages.map(message => (        <div key={message.id}>          <div>{message.role === 'user' ? 'User: ' : 'AI: '}</div>          <div>            {message.parts.map((part, index) => {              if (part.type === 'text') {                return <span key={index}>{part.text}</span>;              }              return null;            })}          </div>        </div>      ))}
      <form onSubmit={handleSubmit}>        <input          value={input}          onChange={e => setInput(e.target.value)}          placeholder="Type a message..."        />        <button type="submit">Send</button>      </form>    </div>  );}

To handle the chat requests and model responses, set up an API route:

app/api/chat/route.ts

import { openai } from '@ai-sdk/openai';import { streamText, convertToModelMessages, UIMessage, stepCountIs } from 'ai';
export async function POST(request: Request) {  const { messages }: { messages: UIMessage[] } = await request.json();
  const result = streamText({    model: openai('gpt-4o'),    system: 'You are a friendly assistant!',    messages: convertToModelMessages(messages),    stopWhen: stepCountIs(5),  });
  return result.toUIMessageStreamResponse();}

This API route uses the streamText function to process chat messages and stream the model's responses back to the client.

Create a Tool

Before enhancing your chat interface with dynamic UI elements, you need to create a tool and corresponding React component. A tool will allow the model to perform a specific action, such as fetching weather information.

Create a new file called ai/tools.ts with the following content:

ai/tools.ts

import { tool as createTool } from 'ai';import { z } from 'zod';
export const weatherTool = createTool({  description: 'Display the weather for a location',  inputSchema: z.object({    location: z.string().describe('The location to get the weather for'),  }),  execute: async function ({ location }) {    await new Promise(resolve => setTimeout(resolve, 2000));    return { weather: 'Sunny', temperature: 75, location };  },});
export const tools = {  displayWeather: weatherTool,};

In this file, you've created a tool called weatherTool. This tool simulates fetching weather information for a given location. This tool will return simulated data after a 2-second delay. In a real-world application, you would replace this simulation with an actual API call to a weather service.

Update the API Route

Update the API route to include the tool you've defined:

app/api/chat/route.ts

import { openai } from '@ai-sdk/openai';import { streamText, convertToModelMessages, UIMessage, stepCountIs } from 'ai';import { tools } from '@/ai/tools';
export async function POST(request: Request) {  const { messages }: { messages: UIMessage[] } = await request.json();
  const result = streamText({    model: openai('gpt-4o'),    system: 'You are a friendly assistant!',    messages: convertToModelMessages(messages),    stopWhen: stepCountIs(5),    tools,  });
  return result.toUIMessageStreamResponse();}

Now that you've defined the tool and added it to your streamText call, let's build a React component to display the weather information it returns.

Create UI Components

Create a new file called components/weather.tsx:

components/weather.tsx

type WeatherProps = {  temperature: number;  weather: string;  location: string;};
export const Weather = ({ temperature, weather, location }: WeatherProps) => {  return (    <div>      <h2>Current Weather for {location}</h2>      <p>Condition: {weather}</p>      <p>Temperature: {temperature}°C</p>    </div>  );};

This component will display the weather information for a given location. It takes three props: temperature, weather, and location (exactly what the weatherTool returns).

Render the Weather Component

Now that you have your tool and corresponding React component, let's integrate them into your chat interface. You'll render the Weather component when the model calls the weather tool.

To check if the model has called a tool, you can check the parts array of the UIMessage object for tool-specific parts. In AI SDK 5.0, tool parts use typed naming: tool-${toolName} instead of generic types.

Update your page.tsx file:

app/page.tsx

'use client';
import { useChat } from '@ai-sdk/react';import { useState } from 'react';import { Weather } from '@/components/weather';
export default function Page() {  const [input, setInput] = useState('');  const { messages, sendMessage } = useChat();
  const handleSubmit = (e: React.FormEvent) => {    e.preventDefault();    sendMessage({ text: input });    setInput('');  };
  return (    <div>      {messages.map(message => (        <div key={message.id}>          <div>{message.role === 'user' ? 'User: ' : 'AI: '}</div>          <div>            {message.parts.map((part, index) => {              if (part.type === 'text') {                return <span key={index}>{part.text}</span>;              }
              if (part.type === 'tool-displayWeather') {                switch (part.state) {                  case 'input-available':                    return <div key={index}>Loading weather...</div>;                  case 'output-available':                    return (                      <div key={index}>                        <Weather {...part.output} />                      </div>                    );                  case 'output-error':                    return <div key={index}>Error: {part.errorText}</div>;                  default:                    return null;                }              }
              return null;            })}          </div>        </div>      ))}
      <form onSubmit={handleSubmit}>        <input          value={input}          onChange={e => setInput(e.target.value)}          placeholder="Type a message..."        />        <button type="submit">Send</button>      </form>    </div>  );}

In this updated code snippet, you:

  1. Use manual input state management with useState instead of the built-in input and handleInputChange.
  2. Use sendMessage instead of handleSubmit to send messages.
  3. Check the parts array of each message for different content types.
  4. Handle tool parts with type tool-displayWeather and their different states (input-available, output-available, output-error).

This approach allows you to dynamically render UI components based on the model's responses, creating a more interactive and context-aware chat experience.

Expanding Your Generative UI Application


You can enhance your chat application by adding more tools and components, creating a richer and more versatile user experience. Here's how you can expand your application:

Adding More Tools

To add more tools, simply define them in your ai/tools.ts file:

// Add a new stock toolexport const stockTool = createTool({  description: 'Get price for a stock',  inputSchema: z.object({    symbol: z.string().describe('The stock symbol to get the price for'),  }),  execute: async function ({ symbol }) {    // Simulated API call    await new Promise(resolve => setTimeout(resolve, 2000));    return { symbol, price: 100 };  },});
// Update the tools objectexport const tools = {  displayWeather: weatherTool,  getStockPrice: stockTool,};

Now, create a new file called components/stock.tsx:

type StockProps = {  price: number;  symbol: string;};
export const Stock = ({ price, symbol }: StockProps) => {  return (    <div>      <h2>Stock Information</h2>      <p>Symbol: {symbol}</p>      <p>Price: ${price}</p>    </div>  );};

Finally, update your page.tsx file to include the new Stock component:

'use client';
import { useChat } from '@ai-sdk/react';import { useState } from 'react';import { Weather } from '@/components/weather';import { Stock } from '@/components/stock';
export default function Page() {  const [input, setInput] = useState('');  const { messages, sendMessage } = useChat();
  const handleSubmit = (e: React.FormEvent) => {    e.preventDefault();    sendMessage({ text: input });    setInput('');  };
  return (    <div>      {messages.map(message => (        <div key={message.id}>          <div>{message.role}</div>          <div>            {message.parts.map((part, index) => {              if (part.type === 'text') {                return <span key={index}>{part.text}</span>;              }
              if (part.type === 'tool-displayWeather') {                switch (part.state) {                  case 'input-available':                    return <div key={index}>Loading weather...</div>;                  case 'output-available':                    return (                      <div key={index}>                        <Weather {...part.output} />                      </div>                    );                  case 'output-error':                    return <div key={index}>Error: {part.errorText}</div>;                  default:                    return null;                }              }
              if (part.type === 'tool-getStockPrice') {                switch (part.state) {                  case 'input-available':                    return <div key={index}>Loading stock price...</div>;                  case 'output-available':                    return (                      <div key={index}>                        <Stock {...part.output} />                      </div>                    );                  case 'output-error':                    return <div key={index}>Error: {part.errorText}</div>;                  default:                    return null;                }              }
              return null;            })}          </div>        </div>      ))}
      <form onSubmit={handleSubmit}>        <input          type="text"          value={input}          onChange={e => setInput(e.target.value)}        />        <button type="submit">Send</button>      </form>    </div>  );}

By following this pattern, you can continue to add more tools and components, expanding the capabilities of your Generative UI application.

On this page

Generative User Interfaces

Build a Generative UI Chat Interface

Basic Chat Implementation

Create a Tool

Update the API Route

Create UI Components

Render the Weather Component

Expanding Your Generative UI Application

Adding More Tools

Deploy and Scale AI Apps with Vercel.

Vercel delivers the infrastructure and developer experience you need to ship reliable AI-powered applications at scale.

Trusted by industry leaders:

  • OpenAI
  • Photoroom
  • leonardo-ai Logoleonardo-ai Logo
  • zapier Logozapier Logo

Talk to an expert