Skip to main content

Overview

@tavus/cvi-ui is a CLI that copies React components, hooks, styles, and optional server helpers into your application. It is not a hosted widget or a runtime CDN package. After you run init and add ..., you import the generated files from your own project tree and can edit them like application code. Use this path when you want Tavus-provided React UI and Daily-powered call state without building every media control yourself. For the fastest no-code UI, use an iframe embed. For fully custom Daily call-object ownership, use the Daily JS / React path.

Blocks

Complete layouts such as Conversation and HairCheck.

Components

Building blocks such as CVIProvider, media controls, captions, chat, and AudioWave.

Hooks

React hooks for call lifecycle, media state, participants, captions, chat, and CVI events.

Server

Server routes and browser helpers that create and end conversations without exposing TAVUS_API_KEY.

Mental model

PieceWhat it doesWhere to read next
npx @tavus/cvi-ui@latest initCreates cvi-components.json, asks about TypeScript, and installs Daily/Jotai dependencies.This page
CVIProviderReact wrapper that provides Daily context to child components and hooks. Put generated CVI UI under this provider.Components
ConversationReact block that renders the Tavus/Daily call UI from a conversationUrl and calls onLeave when the user leaves.Blocks
Server helpersGenerated server routes keep TAVUS_API_KEY server-only, and generated browser helpers call your own backend route.Server
Generated imports are relative to where the CLI copied files in your app. Examples in these docs use paths such as ./components/cvi/components/conversation; adjust them if your cvi-components.json components path is different.

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 - this example already has the HairCheck and Conversation blocks set up.

1. Initialize CVI in Your Project

npx @tavus/cvi-ui@latest init
This command:
  • Creates cvi-components.json, which stores the generated component path and TypeScript preference.
  • Prompts for TypeScript preference.
  • Installs @daily-co/daily-react, @daily-co/daily-js, and jotai.

2. Add CVI Components and Server Helpers

npx @tavus/cvi-ui@latest add conversation
npx @tavus/cvi-ui@latest add tavus-api
add conversation generates the Conversation block and the components/hooks it needs. add tavus-api generates a framework-specific backend route plus lib/tavus-client.ts, which exports createTavusConversation(params?) and endTavusConversation(id). For Vite projects with a server runtime, use:
npx @tavus/cvi-ui@latest add tavus-api-vite-ssr
Plain client-only Vite is unsupported because it would require exposing TAVUS_API_KEY in browser JavaScript. Add a server runtime and use tavus-api-vite-ssr, or create your own backend route.

3. Wrap Your App with the CVI Provider

In your root directory (main.tsx or index.tsx):
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. To create a conversation URL from your app without exposing your TAVUS_API_KEY in the browser, use the server helpers in 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.
Ensure your body element has full dimensions (width: 100% and height: 100%) in your CSS for proper component display.
import { Conversation } from './components/cvi/components/conversation';

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

Complete React example

This example assumes you ran:
npx @tavus/cvi-ui@latest init
npx @tavus/cvi-ui@latest add conversation
npx @tavus/cvi-ui@latest add tavus-api
Set TAVUS_API_KEY only in your server environment. The browser code below calls the generated createTavusConversation and endTavusConversation helpers; those helpers call your generated /api/tavus route.
import { useState } from 'react';
import { CVIProvider } from './components/cvi/components/cvi-provider';
import { Conversation } from './components/cvi/components/conversation';
import {
  createTavusConversation,
  endTavusConversation,
} from './components/cvi/lib/tavus-client';

type TavusConversation = {
  conversation_id: string;
  conversation_url: string;
};

function TavusCall() {
  const [conversation, setConversation] = useState<TavusConversation | null>(null);
  const [isStarting, setIsStarting] = useState(false);
  const [error, setError] = useState<string | null>(null);

  async function startConversation() {
    setIsStarting(true);
    setError(null);

    try {
      const nextConversation = await createTavusConversation({
        persona_id: 'pcb7a34da5fe',
        conversation_name: 'CVI UI example',
      });

      setConversation(nextConversation);
    } catch (error) {
      setError(error instanceof Error ? error.message : 'Failed to start conversation');
    } finally {
      setIsStarting(false);
    }
  }

  async function handleLeave() {
    if (conversation) {
      await endTavusConversation(conversation.conversation_id).catch(() => undefined);
    }

    setConversation(null);
  }

  if (!conversation) {
    return (
      <div>
        <button onClick={startConversation} disabled={isStarting}>
          {isStarting ? 'Starting...' : 'Start conversation'}
        </button>
        {error ? <p role="alert">{error}</p> : null}
      </div>
    );
  }

  return (
    <div style={{ width: '100%', height: '640px' }}>
      <Conversation
        conversationUrl={conversation.conversation_url}
        onLeave={handleLeave}
      />
    </div>
  );
}

export default function App() {
  return (
    <CVIProvider>
      <TavusCall />
    </CVIProvider>
  );
}
Conversation needs a parent with defined dimensions. Give the parent an explicit height, or ensure the full chain (html, body, root element, and container) resolves to a real height.

Documentation Sections

  • Blocks – High-level component compositions and layouts
  • Components – Individual UI components
  • Hooks – Custom React hooks for managing video call state and interactions
  • Server – Server-side helpers (tavus-api, tavus-api-vite-ssr) for creating and ending conversations without exposing your API key