# Quickstart
URL: /getting-started/quickstart

import { ImageZoom } from "fumadocs-ui/components/image-zoom";
import LearnMore from "@/components/learn-more";

Run the starter template to see Tambo in action. You'll register components, add tools, and send messages.

<ImageZoom src="/assets/docs/template.gif" alt="Tambo starter template demo" width={500} height={500} style={{ border: "2px solid #e5e7eb", borderRadius: "8px", width: "80%" }} />

## Set up the starter app

### 1. Create the app

```bash title="Create your tambo project"
npm create tambo-app@latest my-tambo-app
```

This command will:

* Clone the starter template
* Install dependencies
* Initialize git repository
* Walk you through creating a project, generate an API key, and write it to your env file

The CLI prefers `.env.local`, but will update `.env` if that's the only env file present.

<Callout type="info">
  To skip automatic setup, use `--skip-git-init` or `--skip-tambo-init` flags.
</Callout>

### 2. Run the app

```bash title="Run the app"
npm run dev
```

Open `localhost:3000` and start chatting.

## Customize the template

Try making a change to see how registration works.

In `/src/lib/tambo.ts` you'll see how components and tools are registered. In `/src/app/chat/page.tsx` you'll see how they're passed to `TamboProvider`.

### Add a component

Create `src/components/recipe-card.tsx`:

```tsx title="recipe-card.tsx"
"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;
  cookTime?: number;
  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 in `src/lib/tambo.ts`:

```tsx title="tambo.ts"
  {
    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"),
    }),
  },
```

Refresh the page and send "Show me a recipe". Tambo generates and streams your `RecipeCard`.

<ImageZoom src="/assets/docs/recipe-gen.png" alt="RecipeCard rendered by Tambo" width={500} height={500} style={{ border: "2px solid #e5e7eb", borderRadius: "8px", width: "80%" }} />

### Add a tool

The `RecipeCard` above generates recipe data from scratch. Add a tool so Tambo can check what ingredients are available.

In `src/lib/tambo.ts`, add to the tools array:

```tsx title="tambo.ts"
  {
    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",
    ],
    inputSchema: z.object({}),
    outputSchema: z.array(z.string()),
  },
```

Refresh and send "Show me a recipe I can make". Tambo calls the tool, gets the ingredients, and generates a recipe using them.

<ImageZoom src="/assets/docs/add-a-tool.png" alt="Tambo using a tool to get ingredients" width={500} height={500} style={{ border: "2px solid #e5e7eb", borderRadius: "8px", width: "80%" }} />

## Next steps

<LearnMore title="Add Tambo to an existing app" description="Integrate Tambo into your current React project" href="/getting-started/integrate" />

<LearnMore title="Component registration" description="Learn how components and tools work together" href="/concepts/generative-interfaces" />

<LearnMore title="MCP integration" description="Connect external systems with Model Context Protocol" href="/concepts/model-context-protocol" />
