<MagicCanvas /> is the @tavus/cvi-ui component that renders Magic Canvas cards inside your own React app. It listens for Canvas invocations, renders each card, and posts user interactions to Tavus and your webhook.
The Tavus-hosted embed and widget render Canvas automatically and do not require this SDK.
Prerequisites
Attach themagic_canvas skill to your PAL; it enables every component with defaults, and every video conversation on that PAL gets Canvas automatically with nothing to declare on the client. See the Magic Canvas overview.
Installation
@tavus/cvi-ui is a CLI that copies component source into your app, not a runtime dependency.
1
Initialize cvi-ui (skip if already initialized)
cvi-components.json and installs the shared dependencies:
@daily-co/daily-react, @daily-co/daily-js, and jotai.2
Add the component
src/components/cvi/components/magic-canvas
(app/components/cvi/components/magic-canvas if your project has no src
directory); the import paths below resolve from there. If you don’t have a
conversation UI yet, also run:Creating the Conversation
The PAL’s Magic Canvas skill provides the Canvas actions; the create call needs nothing Canvas-specific:Conversations with no rendering surface skip Canvas:
audio_only
conversations, text chats, and conversations with a meeting_url (Zoom,
Teams, Meet) get no Canvas actions: no error and no cards.Mounting the Component
Mount<MagicCanvas /> as a sibling of <Conversation /> inside one CVIProvider (the Daily provider, exactly one per call); both listen on the same call object:
Placement
<MagicCanvas /> renders a fixed full-viewport overlay (position: fixed; inset: 0; pointer-events: none, high z-index) and places each card in the side rail. The overlay never blocks clicks; each card re-enables pointer-events for itself.
Cards cannot mount in your own containers: renderComponent swaps what renders, not where (Bring your own renderer); className restyles the overlay without moving it; onLayoutEffectChange reports the video shift for side panels.
To pin the overlay inside a container instead of the viewport, the examples/vite-app demo uses a CSS override:
position: relative wrapper around <Conversation /> and <MagicCanvas className="canvas-in-player" />. The side-rail card then stays inside the box.
Card Slots
Every card renders inline in thesafe-area-right side rail. Placement is client-side; the model does not choose the slot.
The scheduling_embed card is config-gated: it renders only when the PAL supplies a scheduling provider and a scheduling_url.
Other runtime behavior:
Props
All props are optional;<MagicCanvas /> with no props is fully functional.
onInteraction
on_interaction_callback_failed error event fires and the post still proceeds; the interaction reaches your webhook either way.
onError
Errors never throw into your render tree; they all arrive here.
onLayoutEffectChange
When a side-slot card opens, the canvas reserves a 448px panel and reports how far centered video content should shift to clear it:
active is false below that, and the video stays centered.
Interaction Delivery
Every interaction is posted to:interaction_post_failed error event. Successful interactions arrive at your conversation webhook as canvas.interaction events, and the PAL responds in the conversation.
Bring Your Own Renderer
By default, cards render inside a sandboxed iframe; sandboxed component UIs load from Tavus-approved hosts only. PassrenderComponent (a registry keyed by "<component>@<version>") to render a component natively instead:
component, version, and args (runtime/layout keys already stripped), plus callbacks:
The interaction payload,
canvas.interaction webhook, and auto-dismiss behavior are identical for both paths, and native cards use the same slot and layout model as iframe cards. Components without a matching registry entry keep using the sandboxed iframe.
Complete Example
Adapted fromexamples/vite-app in the cvi-ui repository. The installed tavus-api route expects a JSON body of { "action": "create", "params": { ... } }, forwards params to Tavus verbatim, and is assumed mounted at POST /api/tavus.
main.tsx
App.tsx

