How to Create AI-Powered Applications with Dynamic UI in React Using Tambo

Tambo's React SDK converts LLM-generated instructions into live UI components by maintaining a registry of React components that the AI references, streaming props as they arrive, and rendering them through a hierarchy of context providers.

To create AI-powered applications with dynamic UI in React using Tambo, you wrap your application in the TamboProvider from the @tambo-ai/react package. This provider initializes a component registry that maps LLM output to registered React components, enabling the AI to render interactive elements directly within the chat interface.

Architecture Overview

Tambo implements dynamic UI generation through a layered provider architecture defined in react-sdk/src/v1/providers/tambo-v1-provider.tsx. Each provider handles a specific concern:

  • TamboProvider – The main entry point that composes all sub-providers and holds static SDK configuration.
  • TamboClientProvider – Creates the low-level @tambo-ai/typescript-sdk client and handles authentication.
  • TamboRegistryProvider – Stores component and tool definitions, MCP server metadata, and static resources (source: react-sdk/src/providers/tambo-registry-provider.tsx).
  • TamboStreamProvider – Coordinates per-thread streaming state, run-ids, and message handling (source: react-sdk/src/v1/providers/tambo-v1-stream-context.tsx).
  • TamboThreadInputProvider – Exposes the UI-side useTamboThreadInput hook for sending user messages.
  • TamboMcpProvider / TamboMcpTokenProvider – Optional Model-Context-Protocol (MCP) integration for external tool access.

Core Implementation Steps

Install the SDK

Add the Tambo React SDK and optional peer dependencies for schema validation:

npm install @tambo-ai/react
npm install zod@^4 zod-to-json-schema@^3.25.1

Define UI Components with Zod Schemas

Each component must export a React component and a Zod schema describing its props. The schema enables the SDK to validate streamed props before rendering, as implemented in react-sdk/src/util/registry.ts.

// src/components/WeatherChart.tsx
import { z } from "zod";

export const WeatherChart = ({ data }: { data: { day: string; high: number }[] }) => (
  <div>
    {data.map((d) => (
      <div key={d.day}>
        {d.day}: {d.high}°C
      </div>
    ))}
  </div>
);

// Export the Zod schema for the SDK
export const WeatherChartSchema = z.object({
  data: z.array(z.object({ day: z.string(), high: z.number() })),
});

Register Components and Tools

Pass the component definitions to <TamboProvider> or call registerComponent at runtime via the useTamboRegistry hook. The registry performs validation through validateAndPrepareComponent before storing the definition.

// src/App.tsx
import { TamboProvider } from "@tambo-ai/react";
import { WeatherChart, WeatherChartSchema } from "./components/WeatherChart";

const components = [
  {
    name: "WeatherChart",
    description: "Renders a chart of weather data",
    component: WeatherChart,
    propsSchema: WeatherChartSchema,
  },
];

export default function App() {
  return (
    <TamboProvider
      apiKey={process.env.NEXT_PUBLIC_TAMBO_API_KEY!}
      userKey="user_123"
      components={components}
    >
      <ChatInterface />
    </TamboProvider>
  );
}

For client-side tools, use registerTool with an exec function:

import { useTamboRegistry } from "@tambo-ai/react";

function RegisterTools() {
  const { registerTool } = useTamboRegistry();

  React.useEffect(() => {
    registerTool({
      name: "searchWeb",
      description: "Search the web for a query",
      inputSchema: z.object({ query: z.string() }),
      exec: async ({ query }) => {
        const res = await fetch(`https://api.example.com/search?q=${encodeURIComponent(query)}`);
        return await res.json();
      },
    });
  }, [registerTool]);

  return null;
}

Build the Chat Interface

Use the useTambo hook to access thread state, messages, and streaming status; use useTamboThreadInput to capture user input. These hooks pull from the context providers defined in react-sdk/src/v1/hooks/use-tambo-v1.ts and react-sdk/src/v1/hooks/use-tambo-v1-thread-input.ts.

// src/ChatInterface.tsx
import {
  useTambo,
  useTamboThreadInput,
  Message,
  LoadingIndicator,
} from "@tambo-ai/react";

export function ChatInterface() {
  const { messages, isStreaming } = useTambo();
  const { value, setValue, submit, isPending } = useTamboThreadInput();

  return (
    <form
      onSubmit={async (e) => {
        e.preventDefault();
        await submit();
      }}
    >
      <div className="messages">
        {messages.map((msg) => (
          <Message key={msg.id} message={msg} />
        ))}
        {isStreaming && <LoadingIndicator />}
      </div>

      <input
        value={value}
        onChange={(e) => setValue(e.target.value)}
        placeholder="Ask about the weather…"
      />
      <button disabled={isPending}>Send</button>
    </form>
  );
}

Handle Dynamic Rendering

When the LLM replies with a component content block, the SDK processes it through useTambo() which maintains a componentCacheRef to preserve component identity across re-renders while props stream in. The ComponentRenderer (source: react-sdk/src/v1/components/v1-component-renderer.tsx) validates props against the Zod schema using the ~standard validation protocol and wraps the component with ComponentContentProvider to enable useTamboComponentState for component-specific state management.

When the model emits:

{
  "type": "component",
  "name": "WeatherChart",
  "props": { "data": [{ "day": "Mon", "high": 23 }] }
}

The Message component automatically renders the registered WeatherChart component with validated props, without requiring manual conditional rendering code.

Optional Features

Dynamic Resources and MCP Integration

For AI access to external knowledge bases or tools via the Model Context Protocol (MCP), provide resource handlers to TamboProvider:

<TamboProvider
  apiKey={process.env.NEXT_PUBLIC_TAMBO_API_KEY!}
  userKey="user_123"
  components={components}
  resources={[{ uri: "mydb://products", name: "Product Catalog" }]}
  listResources={async (query) => {
    return [{ uri: "mydb://products/123", name: "Product #123" }];
  }}
  getResource={async (uri) => {
    const res = await fetch(`/api/resource?uri=${encodeURIComponent(uri)}`);
    return await res.json();
  }}
>
  <ChatInterface />
</TamboProvider>

The SDK exposes useTamboMcpServerInfos to list configured MCP servers and useTamboMcpResource for fetching resources. These are managed by TamboMcpProvider and TamboMcpTokenProvider within the provider hierarchy.

Key Source Files and Implementation Details

Understanding the internal architecture helps when extending the SDK or debugging streaming issues:

Purpose File Path
Main SDK entry point react-sdk/src/v1/providers/tambo-v1-provider.tsx
Component & tool registry react-sdk/src/providers/tambo-registry-provider.tsx
Primary hook (combined context) react-sdk/src/v1/hooks/use-tambo-v1.ts
Component renderer (streams props, validates schema) react-sdk/src/v1/components/v1-component-renderer.tsx
Thread-input hook (user message UI) react-sdk/src/v1/hooks/use-tambo-v1-thread-input.ts
Stream-state context (per-thread event handling) react-sdk/src/v1/providers/tambo-v1-stream-context.tsx
Utility: validate component props with Zod react-sdk/src/util/registry.ts
MCP utilities (optional) react-sdk/src/mcp/*

The registry validation in validateAndPrepareComponent automatically attaches Zod schemas using the ~standard protocol, enabling runtime type safety for AI-generated props.

Summary

  • TamboProvider initializes the SDK hierarchy, requiring apiKey and userKey for authentication and thread ownership.
  • Component registration requires a React component, Zod schema (propsSchema), and metadata; validation occurs in react-sdk/src/util/registry.ts.
  • useTambo provides thread state, streaming status, and rendered components via componentCacheRef for stable identities.
  • useTamboThreadInput handles user message submission with pending states.
  • ComponentRenderer validates streaming props against Zod schemas and wraps components with context providers.
  • MCP integration enables external tool access through resources, listResources, and getResource props on TamboProvider.

Frequently Asked Questions

How does Tambo validate props for AI-generated components?

Tambo validates props using Zod schemas attached during component registration. When the LLM streams a component block, the ComponentRenderer (in react-sdk/src/v1/components/v1-component-renderer.tsx) calls the ~standard validation protocol on the registered component's schema. If validation fails, the component does not render, preventing runtime errors from malformed AI output.

What is the difference between useTambo and useTamboThreadInput?

useTambo (defined in react-sdk/src/v1/hooks/use-tambo-v1.ts) provides access to the complete thread state, including message history, streaming status, rendered components, and thread management actions like startNewThread. useTamboThreadInput (in react-sdk/src/v1/hooks/use-tambo-v1-thread-input.ts) specifically manages the user input field state and submission handling, providing value, setValue, submit, and isPending for form UI construction.

Can I register components dynamically after the initial render?

Yes, you can register components dynamically using the useTamboRegistry hook, which exposes registerComponent and registerTool functions. These functions call the validation utilities in react-sdk/src/util/registry.ts to attach schemas and metadata to the internal componentList map. This enables runtime extension of the AI's available UI components without reloading the application.

How does Tambo handle streaming component props?

Tambo maintains component identity across streaming updates using a componentCacheRef inside useTambo (react-sdk/src/v1/hooks/use-tambo-v1.ts). As the LLM streams partial JSON for component props, the SDK updates the cached component's props while preserving the React component instance. The ComponentRenderer then receives these streaming props, validates them against the Zod schema, and re-renders the component with new data without unmounting it.

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 →