> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tavus.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Embed Conversational Video Interface

> Learn how to embed Tavus's Conversational Video Interface (CVI) into your site or app.

## Overview

Tavus CVI delivers AI-powered video conversations directly in your application. You can integrate it using:

| Method               | When to choose this                                                                                                                             | Production suitability                                                                                                                                      | Dependencies                                                 |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| **iframe**           | Fastest working UI for demos, prototypes, and simple production embeds. Create a conversation and set `src` to the returned `conversation_url`. | Good when Tavus's default room UI is enough.                                                                                                                | None beyond your backend create-conversation route.          |
| **@tavus/cvi-ui**    | Recommended React component path when you want Tavus-provided components, hooks, and styling control.                                           | Good for React apps that want a faster path than building directly on Daily.                                                                                | `@tavus/cvi-ui` CLI plus installed Daily React dependencies. |
| **Daily JS / React** | Use when your app needs deeper control over room state, events, or a fully custom in-call UI.                                                   | Good for teams comfortable owning more call UI and state.                                                                                                   | `@daily-co/daily-js` and optionally Daily React.             |
| **LiveKit Agent**    | Use only if you already run a LiveKit Agents pipeline and want Tavus as the avatar video layer.                                                 | Not the recommended path for most CVI embeds; LiveKit only provides rendering, while Tavus's Full Pipeline includes perception, turn-taking, and rendering. | LiveKit Agents app plus Tavus LiveKit integration.           |

Every flow below needs a join URL: call [Create Conversation](/api-reference/conversations/create-conversation) with a valid [Authentication](/api-reference/authentication) header and read the **`conversation_url`** string from the response. Use that value wherever the samples use **`conversation_url`** (JavaScript) or **`YOUR_CONVERSATION_URL`** (string placeholders in HTML or query strings).

<Info>
  For the quickest path, use an iframe with
  `allow="camera; microphone; fullscreen; display-capture; autoplay"`.
</Info>

<Note>
  For private rooms, create the conversation with `require_auth: true` and use
  the returned `meeting_token`. Append it to the iframe URL as `?t=TOKEN`, or
  pass it to Daily with `join({ url: conversation_url, token: meeting_token })`.
</Note>

## Implementation Steps

<Tabs>
  <Tab title="@tavus/cvi-ui (Component Library)">
    This method provides a full-featured React component library. It offers pre-built, customizable components and hooks for embedding Tavus CVI in your app.

    `@tavus/cvi-ui` is a CLI that copies React source files into your project. It is not a hosted widget: you import generated files from your app, wrap UI in `CVIProvider`, render `Conversation` with a Tavus `conversation_url`, and use generated server helpers to keep `TAVUS_API_KEY` off the client. For the complete reference and lifecycle example, see the [component library overview](/sections/conversational-video-interface/component-library/overview), [blocks](/sections/conversational-video-interface/component-library/blocks), [components](/sections/conversational-video-interface/component-library/components), [hooks](/sections/conversational-video-interface/component-library/hooks), and [server helpers](/sections/conversational-video-interface/component-library/server).

    ## React component library (@tavus/cvi-ui)

    The Tavus Conversational Video Interface (CVI) React component library provides a complete set of pre-built components and hooks for integrating AI-powered video conversations into your React applications. This library simplifies setting up Tavus in your codebase, allowing you to focus on your application's core features.

    Key features include:

    * **Pre-built video chat components**
    * **Device management** (camera, microphone, screen sharing)
    * **Real-time audio/video processing**
    * **Customizable styling** and theming
    * **TypeScript support** with full type definitions

    ***

    ## Quick Start

    ### Prerequisites

    Before getting started, ensure you have a React project set up.

    Alternatively, you can start from our example project: [CVI UI Haircheck Conversation Example](https://github.com/Tavus-Engineering/tavus-examples/tree/main/examples/cvi-ui-haircheck-conversation) - this example already has the HairCheck and Conversation blocks set up.

    ### 1. Initialize CVI in Your Project

    ```bash theme={null}
    npx @tavus/cvi-ui@latest init
    ```

    * Creates a `cvi-components.json` config file
    * Prompts for TypeScript preference
    * Installs npm dependencies (@daily-co/daily-react, @daily-co/daily-js, jotai)

    ### 2. Add CVI Components

    ```bash theme={null}
    npx @tavus/cvi-ui@latest add conversation
    ```

    ### 3. Wrap Your App with the CVI Provider

    In your root directory (main.tsx or index.tsx):

    ```tsx theme={null}
    import { CVIProvider } from './components/cvi/components/cvi-provider';

    function App() {
      return <CVIProvider>{/* Your app content */}</CVIProvider>;
    }
    ```

    ### 4. Add a Conversation Component

    Learn how to create a conversation URL at [https://docs.tavus.io/api-reference/conversations/create-conversation](https://docs.tavus.io/api-reference/conversations/create-conversation). To create a conversation URL from your app without exposing your `TAVUS_API_KEY` in the browser, use the server helpers in [Server](/sections/conversational-video-interface/component-library/server) (`tavus-api` for Next.js/Remix/TanStack Start, or `tavus-api-vite-ssr` for Vite-with-server).

    **Note:** The Conversation component requires a parent container with defined dimensions to display properly.

    <Info>
      Ensure your body element has full dimensions (`width: 100%` and `height:
              100%`) in your CSS for proper component display.
    </Info>

    ```tsx theme={null}
    import { Conversation } from './components/cvi/components/conversation';

    function CVI() {
      const conversation_url = 'YOUR_CONVERSATION_URL';
      const handleLeave = () => {
        // handle leave
      };
      return (
        <div
          style={{
            width: '100%',
            height: '100%',
            maxWidth: '1200px',
            margin: '0 auto',
          }}
        >
          <Conversation
            conversationUrl={conversation_url}
            onLeave={handleLeave}
          />
        </div>
      );
    }
    ```

    ***

    ## Documentation Sections

    * **[Overview](/sections/conversational-video-interface/component-library/overview)** – Overview of the CVI component library
    * **[Blocks](/sections/conversational-video-interface/component-library/blocks)** – High-level component compositions and layouts
    * **[Components](/sections/conversational-video-interface/component-library/components)** – Individual UI components
    * **[Hooks](/sections/conversational-video-interface/component-library/hooks)** – Custom React hooks for managing video call state and interactions
    * **[Server](/sections/conversational-video-interface/component-library/server)** – Server-side helpers (`tavus-api`, `tavus-api-vite-ssr`) for creating and ending conversations without exposing your API key
  </Tab>

  <Tab title="iframe">
    This is the simplest approach requiring no coding. It leverages Tavus’s prebuilt interface with limited customization options.

    1. Create a conversation using the Tavus API.
    2. Set `src` below to the **`conversation_url`** from the response (same value as **`YOUR_CONVERSATION_URL`** in the snippet):

    ```html theme={null}
    <!DOCTYPE html>
    <html>
      <head><title>Tavus CVI</title></head>
      <body>
        <iframe
          src="YOUR_CONVERSATION_URL"
          allow="camera; microphone; fullscreen; display-capture; autoplay"
          style="width: 100%; height: 500px; border: none;">
        </iframe>
      </body>
    </html>
    ```
  </Tab>

  <Tab title="Vanilla JavaScript">
    This method provides basic customizations and dynamic room management for apps without framework.

    1. Add the following script tag to your HTML `<head>`:

    ```html theme={null}
    <head>
      <script src="https://unpkg.com/@daily-co/daily-js"></script>
    </head>
    ```

    2. Use the following script. Set **`conversation_url`** to the **`conversation_url`** value from [Create Conversation](/api-reference/conversations/create-conversation) (or keep **`YOUR_CONVERSATION_URL`** as a temporary literal until you wire your backend).

    ```html theme={null}
    <body>
      <div id="video-call-container"></div>
      <script>
        const conversation_url = 'YOUR_CONVERSATION_URL';

        // Create a Daily iframe with custom settings
        const callFrame = window.Daily.createFrame({
          iframeStyle: {
            width: '100%',
            height: '500px',
          },
        });

        // Join the Tavus CVI conversation
        callFrame.join({ url: conversation_url });

        // Append the iframe to the container
        document.getElementById('video-call-container').appendChild(callFrame.iframe());
      </script>
    </body>

    ```
  </Tab>

  <Tab title="Node.js + Express">
    This method serves dynamic pages that embed Tavus CVI within Daily rooms.

    1. Install Express:

    ```bash theme={null}
    npm install express
    ```

    2. Create `server.js` and implement the following Express server:

    ```js theme={null}
    const express = require('express');
    const app = express();
    const PORT = 3000;

    app.get('/room', (req, res) => {
      const conversation_url = req.query.url || 'YOUR_CONVERSATION_URL';
      res.send(`
        <!DOCTYPE html>
        <html>
          <head>
            <script src="https://unpkg.com/@daily-co/daily-js"></script>
          </head>
          <body>
            <div id="video-call-container"></div>
            <script>
              // Create the Daily iframe for the Tavus CVI room
              const callFrame = window.Daily.createFrame({
                iframeStyle: {
                  width: '100%',
                  height: '500px',
                },
              });

              // Join the room
              callFrame.join({ url: '${conversation_url}' });

              // Append the iframe to the container
              document.getElementById('video-call-container').appendChild(callFrame.iframe());
            </script>
          </body>
        </html>
      `);
    });

    app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));
    ```

    3. Run the server:

    ```bash theme={null}
    node server.js
    ```

    4. Visit: `http://localhost:3000/room?url=YOUR_CONVERSATION_URL` (URL-encode the **`conversation_url`** query value in real usage)

    <Note>
      ### Notes

      * Supports dynamic URLs.
      * Can be extended with authentication and other logic using Tavus's API.
    </Note>
  </Tab>

  <Tab title="React + Daily (createCallObject)">
    This method uses **React** together with **`@daily-co/daily-js`** (`createCallObject`) so you can build a fully custom in-call UI while still joining the same Tavus CVI **`conversation_url`** as the other tabs.

    1. Install SDK:

    ```bash theme={null}
    npm install @daily-co/daily-js
    ```

    2. Use the following script to join the Tavus CVI conversation:

    ```js [expandable] theme={null}
    import React, { useEffect, useRef, useState } from 'react';
    import DailyIframe from '@daily-co/daily-js';

    const conversation_url = 'YOUR_CONVERSATION_URL';

    const getOrCreateCallObject = () => {
      // Use a property on window to store the singleton
      if (!window._dailyCallObject) {
        window._dailyCallObject = DailyIframe.createCallObject();
      }
      return window._dailyCallObject;
    };


    const App = () => {
      const callRef = useRef(null);
      const [remoteParticipants, setRemoteParticipants] = useState({});


      useEffect(() => {
        // Only create or get one call object per page
        const call = getOrCreateCallObject();
        callRef.current = call;


        // Join meeting (conversation_url from Create Conversation response)
        call.join({ url: conversation_url });


        // Handle remote participants
        const updateRemoteParticipants = () => {
          const participants = call.participants();
          const remotes = {};
          Object.entries(participants).forEach(([id, p]) => {
            if (id !== 'local') remotes[id] = p;
          });
          setRemoteParticipants(remotes);
        };


        call.on('participant-joined', updateRemoteParticipants);
        call.on('participant-updated', updateRemoteParticipants);
        call.on('participant-left', updateRemoteParticipants);


        // Cleanup
        return () => {
          call.leave();
        };
      }, []);


      // Attach remote video and audio tracks
      useEffect(() => {
        Object.entries(remoteParticipants).forEach(([id, p]) => {
          // Video
          const videoEl = document.getElementById(`remote-video-${id}`);
          if (videoEl && p.tracks.video && p.tracks.video.state === 'playable' && p.tracks.video.persistentTrack
          ) {
            videoEl.srcObject = new MediaStream([p.tracks.video.persistentTrack]);
          }
          // Audio
          const audioEl = document.getElementById(`remote-audio-${id}`);
          if (
            audioEl && p.tracks.audio && p.tracks.audio.state === 'playable' && p.tracks.audio.persistentTrack
          ) {
            audioEl.srcObject = new MediaStream([p.tracks.audio.persistentTrack]);
          }
        });
      }, [remoteParticipants]);


      // Custom UI
      return (
        <div className="min-h-screen bg-gray-900 text-white flex flex-col">
          <header className="bg-gray-800 p-4 flex justify-between items-center">
            <span className="font-semibold">Meeting Room (daily-js custom UI)</span>
          </header>
          <main className="flex-1 p-4 grid grid-cols-2 md:grid-cols-4 gap-2">
          {Object.entries(remoteParticipants).map(([id, p]) => (
            <div
              key={id}
              className="relative bg-gray-800 rounded-lg overflow-hidden aspect-video w-48"
            >
              <video
                id={`remote-video-${id}`}
                autoPlay
                playsInline
                className="w-1/3 h-1/3 object-contain mx-auto"
              />
              <audio id={`remote-audio-${id}`} autoPlay playsInline />
              <div className="absolute bottom-2 left-2 bg-black bg-opacity-50 px-2 py-1 rounded text-sm">
                {p.user_name || id.slice(-4)}
              </div>
            </div>
          ))}
        </main>
        </div>
      );
    };


    export default App;
    ```

    3. Customize the conversation UI in the script above (Optional). See the <a href="https://docs.daily.co/guides/customizing-in-call-ui" target="_blank">Daily JS SDK</a> for details.

    ### Daily JS / React implementation notes

    Use Daily directly when your team wants to customize the room UI, participant state, event handling, layout, analytics hooks, or other call behavior around the Tavus-created `conversation_url`. If you want Tavus-provided React components instead of managing Daily call objects yourself, use the [CVI component library](/sections/conversational-video-interface/component-library/overview).

    Install the Daily package that matches the level of control you need:

    ```bash theme={null}
    npm install @daily-co/daily-js
    ```

    If you are using Daily React hooks or components directly, also install:

    ```bash theme={null}
    npm install @daily-co/daily-react
    ```

    In React 18 development mode, Strict Mode and hot reload can expose duplicate-call-object bugs. Keep one Daily call object per browser window, pass `token` only when `meeting_token` is a string, and clean up the call object when the user leaves.

    ```tsx theme={null}
    import { useEffect, useState } from 'react';
    import DailyIframe from '@daily-co/daily-js';

    type TavusConversationJoin = {
      conversation_url: string;
      meeting_token?: string | null;
    };

    type DailyCallObject = ReturnType<typeof DailyIframe.createCallObject>;

    declare global {
      interface Window {
        __tavusDailyCallObject?: DailyCallObject | null;
      }
    }

    function getOrCreateCallObject() {
      if (!window.__tavusDailyCallObject) {
        window.__tavusDailyCallObject = DailyIframe.createCallObject();
      }

      return window.__tavusDailyCallObject;
    }

    function getJoinOptions(conversation: TavusConversationJoin) {
      const joinOptions: { url: string; token?: string } = {
        url: conversation.conversation_url,
      };

      if (
        typeof conversation.meeting_token === 'string' &&
        conversation.meeting_token.length > 0
      ) {
        joinOptions.token = conversation.meeting_token;
      }

      return joinOptions;
    }

    export function CustomDailyRoom({
      conversation,
    }: {
      conversation: TavusConversationJoin;
    }) {
      const [error, setError] = useState<string | null>(null);

      useEffect(() => {
        let isUnmounted = false;
        const call = getOrCreateCallObject();

        call.join(getJoinOptions(conversation)).catch((error) => {
          if (!isUnmounted) {
            setError(error instanceof Error ? error.message : 'Failed to join call');
          }
        });

        return () => {
          isUnmounted = true;
          call.leave().catch(() => undefined);
          call.destroy().catch(() => undefined);

          if (window.__tavusDailyCallObject === call) {
            window.__tavusDailyCallObject = null;
          }
        };
      }, [conversation.conversation_url, conversation.meeting_token]);

      if (error) {
        return <p role="alert">{error}</p>;
      }

      return <div id="custom-daily-room">{/* Render your custom call UI here. */}</div>;
    }
    ```

    <Note>
      `meeting_token` is only returned for private rooms created with
      `require_auth: true`. See [Private Rooms](/sections/conversational-video-interface/conversation/customizations/private-rooms)
      for iframe `?t=` usage and Daily SDK token joins.
    </Note>
  </Tab>
</Tabs>

## FAQs

<AccordionGroup>
  <Accordion title="How can I reduce background noise during calls?" defaultOpen="true">
    Tavus provides a built-in voice isolation feature that separates speech from background noise in the participant's microphone audio. You can enable it via the `voice_isolation` parameter in the Conversational Flow layer of your persona.

    Learn more in our [Voice Isolation documentation](/sections/conversational-video-interface/persona/conversational-flow#4-voice_isolation).
  </Accordion>

  <Accordion title="Can I add event listeners to the call client?" defaultOpen="true">
    Yes, you can attach <a href="https://docs.daily.co/reference/daily-js/events" target="_blank">Daily event listeners</a> to monitor and respond to events like participants joining, leaving, or starting screen share.
  </Accordion>
</AccordionGroup>
