Start Here
Get an introduction to Tambo's features by using our starter app
Download and run our generative UI chat template to get an understanding of the fundamental features of Tambo. This application will show you how to build generative UI, integrate tools, send messages to Tambo, and stream responses.

Template Installation
1. Download the code
npm create tambo-app@latest my-tambo-app && cd my-tambo-app
This will copy the source code of the template app into your directory and install the dependencies. See the source repo here.
2. Get a Tambo API key
npx tambo init
To send messages to Tambo, you need to create a Project through Tambo and generate an API key to send with requests. This command will walk you through the setup of your first Tambo project, generate an API key, and set the API key in your project automatically.
3. Run the app
npm run dev
Start the app and go to localhost:3000
in your browser to start sending messages to Tambo!
Customize
To get a better understanding of what's happening in this application, try to make a change to update Tambo's capabilities.
In /src/lib/tambo.ts
you'll see how the template registers components and tools with Tambo. In /src/app/chat/page.tsx
you'll see how those tools and components are passed to the TamboProvider
to 'register' them.
Add a component
Let's create and register a new component with Tambo to give our AI chat a new feature.
In src/components
create a new file called recipe-card.tsx
and paste the following code into it:
"use client";
import { ChefHat, Clock, Minus, Plus, Users } from "lucide-react";
import { useState } from "react";
interface Ingredient {
name: string;
amount: number;
unit: string;
}
interface RecipeCardProps {
title?: string;
description?: string;
ingredients?: Ingredient[];
prepTime?: number; // in minutes
cookTime?: number; // in minutes
originalServings?: number;
}
export default function RecipeCard({
title,
description,
ingredients,
prepTime = 0,
cookTime = 0,
originalServings,
}: RecipeCardProps) {
const [servings, setServings] = useState(originalServings || 1);
const scaleFactor = servings / (originalServings || 1);
const handleServingsChange = (newServings: number) => {
if (newServings > 0) {
setServings(newServings);
}
};
const formatTime = (minutes: number) => {
if (minutes < 60) {
return `${minutes}m`;
}
const hours = Math.floor(minutes / 60);
const remainingMinutes = minutes % 60;
return remainingMinutes > 0
? `${hours}h ${remainingMinutes}m`
: `${hours}h`;
};
const totalTime = prepTime + cookTime;
return (
<div className="bg-white max-w-md rounded-xl shadow-lg overflow-hidden border border-gray-200">
<div className="p-6">
<div className="mb-4">
<h2 className="text-2xl font-bold text-gray-900 mb-2">{title}</h2>
<p className="text-gray-600 text-sm leading-relaxed">{description}</p>
</div>
<div className="flex items-center gap-6 mb-6 text-sm text-gray-600">
<div className="flex items-center gap-2">
<Clock className="w-4 h-4" />
<span>{formatTime(totalTime)}</span>
</div>
<div className="flex items-center gap-2">
<Users className="w-4 h-4" />
<span>{servings} servings</span>
</div>
{prepTime > 0 && (
<div className="flex items-center gap-2">
<ChefHat className="w-4 h-4" />
<span>Prep: {formatTime(prepTime)}</span>
</div>
)}
</div>
<div className="mb-6 p-4 bg-gray-50 rounded-lg">
<div className="flex items-center justify-between">
<span className="font-medium text-gray-700">Adjust Servings:</span>
<div className="flex items-center gap-3">
<button
onClick={() => handleServingsChange(servings - 1)}
className="p-2 rounded-full bg-white border border-gray-300 hover:bg-gray-50 transition-colors"
disabled={servings <= 1}
>
<Minus className="w-4 h-4" />
</button>
<span className="font-semibold text-lg min-w-[3rem] text-center">
{servings}
</span>
<button
onClick={() => handleServingsChange(servings + 1)}
className="p-2 rounded-full bg-white border border-gray-300 hover:bg-gray-50 transition-colors"
>
<Plus className="w-4 h-4" />
</button>
</div>
</div>
</div>
<div className="mb-6">
<h3 className="text-lg font-semibold text-gray-900 mb-3">
Ingredients
</h3>
<ul className="space-y-2">
{ingredients?.map((ingredient, index) => (
<li
key={index}
className="flex items-center gap-3 p-2 rounded-md hover:bg-gray-50 transition-colors"
>
<div className="w-2 h-2 bg-orange-500 rounded-full flex-shrink-0" />
<span className="font-medium text-gray-900">
{(ingredient.amount * scaleFactor).toFixed(
(ingredient.amount * scaleFactor) % 1 === 0 ? 0 : 1,
)}
</span>
<span className="text-gray-600">{ingredient.unit}</span>
<span className="text-gray-800">{ingredient.name}</span>
</li>
))}
</ul>
</div>
</div>
</div>
);
}
Register it with Tambo by updating the components
array in src/lib/tambo.ts
with the following entry:
{
name: "RecipeCard",
description: "A component that renders a recipe card",
component: RecipeCard,
propsSchema: z.object({
title: z.string().describe("The title of the recipe"),
description: z.string().describe("The description of the recipe"),
prepTime: z.number().describe("The prep time of the recipe in minutes"),
cookTime: z.number().describe("The cook time of the recipe in minutes"),
originalServings: z
.number()
.describe("The original servings of the recipe"),
ingredients: z
.array(
z.object({
name: z.string().describe("The name of the ingredient"),
amount: z.number().describe("The amount of the ingredient"),
unit: z.string().describe("The unit of the ingredient"),
})
)
.describe("The ingredients of the recipe"),
}),
},
Now refresh the browser page and send a message like "Show me a recipe" and you should see Tambo generate and stream in an instance of your RecipeCard
component.

Add a tool
You might notice that when using our added RecipeCard
component above, Tambo generates recipe data completely from scratch. To allow Tambo to retrieve the list of ingredients we actually have, we can add a tool to get them.
In src/lib/tambo.ts
add the following entry to the 'tools' array:
{
name: "get-available-ingredients",
description:
"Get a list of all the available ingredients that can be used in a recipe.",
tool: () => [
"pizza dough",
"mozzarella cheese",
"tomatoes",
"basil",
"olive oil",
"chicken breast",
"ground beef",
"onions",
"garlic",
"bell peppers",
"mushrooms",
"pasta",
"rice",
"eggs",
"bread",
],
toolSchema: z.function().returns(z.array(z.string())),
},
Now refresh the browser page and send a message like "Show me a recipe I can make" and you should see Tambo look for the available ingredients and then generate a RecipeCard
using them.

Going Further
This template app just scratches the surface of what you can build with Tambo. By using Tambo in creative ways you can make truly magical custom user experiences!