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

> Learn to build AI-powered React apps with dynamic UIs using Tambo. Tambo's SDK converts LLM instructions into live React components, streaming props as they arrive. Integrate AI seamlessly into your frontend.

- Repository: [tambo ai/tambo](https://github.com/tambo-ai/tambo)
- Tags: how-to-guide
- Published: 2026-02-16

---

**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`](https://github.com/tambo-ai/tambo/blob/main/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`](https://github.com/tambo-ai/tambo/blob/main/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`](https://github.com/tambo-ai/tambo/blob/main/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:

```bash
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`](https://github.com/tambo-ai/tambo/blob/main/react-sdk/src/util/registry.ts).

```tsx
// 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.

```tsx
// 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:

```tsx
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`](https://github.com/tambo-ai/tambo/blob/main/react-sdk/src/v1/hooks/use-tambo-v1.ts) and [`react-sdk/src/v1/hooks/use-tambo-v1-thread-input.ts`](https://github.com/tambo-ai/tambo/blob/main/react-sdk/src/v1/hooks/use-tambo-v1-thread-input.ts).

```tsx
// 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`](https://github.com/tambo-ai/tambo/blob/main/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:

```json
{
  "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`:

```tsx
<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`](https://github.com/tambo-ai/tambo/blob/main/react-sdk/src/v1/providers/tambo-v1-provider.tsx) |
| Component & tool registry | [`react-sdk/src/providers/tambo-registry-provider.tsx`](https://github.com/tambo-ai/tambo/blob/main/react-sdk/src/providers/tambo-registry-provider.tsx) |
| Primary hook (combined context) | [`react-sdk/src/v1/hooks/use-tambo-v1.ts`](https://github.com/tambo-ai/tambo/blob/main/react-sdk/src/v1/hooks/use-tambo-v1.ts) |
| Component renderer (streams props, validates schema) | [`react-sdk/src/v1/components/v1-component-renderer.tsx`](https://github.com/tambo-ai/tambo/blob/main/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`](https://github.com/tambo-ai/tambo/blob/main/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`](https://github.com/tambo-ai/tambo/blob/main/react-sdk/src/v1/providers/tambo-v1-stream-context.tsx) |
| Utility: validate component props with Zod | [`react-sdk/src/util/registry.ts`](https://github.com/tambo-ai/tambo/blob/main/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`](https://github.com/tambo-ai/tambo/blob/main/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`](https://github.com/tambo-ai/tambo/blob/main/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`](https://github.com/tambo-ai/tambo/blob/main/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`](https://github.com/tambo-ai/tambo/blob/main/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`](https://github.com/tambo-ai/tambo/blob/main/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`](https://github.com/tambo-ai/tambo/blob/main/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.