View as markdown

<AvatarWidget />

The main component. Wraps a LiveKit session, mounts the avatar UI, routes agent commands, manages display state. Lives at @livelayer/react.

tsx
import { AvatarWidget } from "@livelayer/react";
import "@livelayer/react/styles.css";

<AvatarWidget agentId="agt_abc123" />

Props by category

Connection

NameTypeDescription
agentId*stringThe published agent ID from app.livelayer.studio.
apiKeystringOptional API key for cross-origin auth. Usually unnecessary; only set this if you self-host or use a custom session endpoint.
baseUrlstringAPI base URL. Default: https://app.livelayer.studio. Override for self-hosted.
sessionEndpointstringCustom endpoint that returns { url, token } for LiveKit session. Use this when your server mints tokens.
sessionBodyRecord<string, unknown>Extra body fields included in the session POST. Useful for passing your own auth context (userId, plan, etc.).

Display mode

The widget can be in three states: hidden, minimized (bubble), or expanded (full panel).

NameTypeDescription
displayMode"hidden" | "minimized" | "expanded"Controlled display state. Pass this to manage display from outside the component.
defaultDisplayMode"hidden" | "minimized" | "expanded"Initial mode before the user toggles. Default: "expanded".
onDisplayModeChange(mode: DisplayMode) => voidCallback fired when the user toggles between minimized/expanded/hidden.
experienceMode"WIDGET" | "EMBEDDED"Layout type. WIDGET = floating, position-aware. EMBEDDED = inline, fills parent.
position"bottom-right" | "bottom-left" | "top-right" | "top-left" | "custom"Where the floating bubble docks. Use "custom" + className/style for full position control.
mobileBreakpointnumber | falseWidth threshold (px) for mobile layout. Default: 640. Pass false to disable mobile layout entirely.
persistKeystringlocalStorage key for saving display mode. Default: "ll-widget". Use distinct keys when rendering multiple widgets.
disablePersistencebooleanSkip localStorage persistence entirely.

Branding

tsx
<AvatarWidget
  agentId="agt_abc123"
  branding={{
    logoUrl: "/logo.svg",
    productName: "Acme Assistant",
    primaryColor: "#0EA5E9",
    accentColor: "#06B6D4",
    backgroundColor: "#FFFFFF",
    textColor: "#0F172A",
  }}
/>
NameTypeDescription
brandingBrandingConfigLogo, product name, color palette. See BrandingConfig type.
avatarImageUrlstringOverride the agent's still avatar image (shown when video isn't loaded yet).
idleLoopUrlstringOverride the idle animation video URL.
agentNamestringOverride the displayed agent name.
greetingstringOverride the dashboard-configured greeting on connect.

Capabilities

Restrict what page actions the agent is allowed to perform. Same idea as the script-tag capabilities, but type-safe and finer-grained — the React package supports form filling.

tsx
import type { AgentCapability } from "@livelayer/react";

const capabilities: AgentCapability[] = [
  "navigate",
  "scroll",
  "click",
  "fill_forms",
  "submit_forms",
  "read_page",
];

<AvatarWidget agentId="agt_abc123" capabilities={capabilities} />
NameTypeDescription
capabilitiesAgentCapability[]Allowlist of agent actions. Omit for unrestricted (back-compat). Available: navigate, scroll, click, fill_forms, submit_forms, read_page.
allowMicbooleanShow the mic button in the toolbar. Default: true.
allowCamerabooleanShow the camera button. Default: false (most agents are voice-only).
allowScreenSharebooleanShow the screen-share button. Default: false.
allowTypingbooleanShow the text input. Default: true.

Routing (SPA)

For Next.js App Router, React Router v6+, or any SPA where the URL changes without a full reload, pass pathname and wire onNavigate. Without these, the widget falls back to native anchor clicks.

NameTypeDescription
pathnamestringREQUIRED for Next.js App Router and React Router v6+. The current pathname. The widget uses this for showOn/hideOn evaluation and to invalidate caches between routes.
showOnRoutePattern[]Patterns where the widget MAY render. RoutePattern = string (glob), RegExp, or function. Empty/omitted = render everywhere.
hideOnRoutePattern[]Patterns where the widget will NOT render. Wins over showOn.
onNavigate(href: string) => voidCalled when the agent emits a navigate command. Wire to router.push for SPA navigation; default falls back to anchor click + history.pushState.
onScrollToSelector(selector: string, behavior?: "smooth" | "instant") => voidCalled on scroll_to. Default scrolls the matched element into view.
onScrollPage(direction: "up" | "down" | "top" | "bottom", behavior?: "smooth" | "instant") => voidCalled on scroll_page. Default scrolls the document.
onClick(selector: string) => voidCalled on click commands. Default does document.querySelector(selector).click().

See Routing for full examples.

Page awareness

When the agent calls request_page_context, these props control what gets sent.

NameTypeDescription
getPageContext(extras?) => PageContext | Promise<PageContext>Custom DOM walker. Default extracts headings, links, form fields, visible text, marked regions. Override to feed the agent your structured app state.
pageContextExtrasRecord<string, unknown>Free-form metadata always included in page context (e.g., currentUser, cartTotal).
getRoutes() => RouteEntryInput[] | Promise<RouteEntryInput[]>Override the route extraction. Default walks <a> elements; pass this to feed routes from your sitemap or API.

See Page awareness for end-to-end examples.

Lifecycle callbacks

tsx
<AvatarWidget
  agentId="agt_abc123"
  onConnect={() => analytics.track("agent_connected")}
  onDisconnect={() => analytics.track("agent_disconnected")}
  onTranscript={(entries) => myStore.setTranscript(entries)}
  onAgentState={(state) => myStore.setAgentState(state)}
  onConnectionStateChange={(state) => console.log("conn:", state)}
/>
NameTypeDescription
onConnect() => voidSession connected.
onDisconnect() => voidSession disconnected.
onTranscript(entries: TranscriptEntry[]) => voidTranscript updated. Fires for both partial and final entries.
onAgentState(state: AgentState) => voidAgent state changed: idle | listening | thinking | speaking.
onConnectionStateChange(state: ConnectionState) => voidConnection state changed: idle | connecting | connected | disconnected | error.
onAgentEvent(e: AgentEventDetail) => voidObservability sink. Fires for every agent event (navigation, scroll, fill, submit, custom). Useful for analytics or debugging.
onAgentCommand(cmd: AgentCommand) => voidReceives non-universal agent commands — anything you defined in the dashboard's tool config that isn't navigate/scroll/click/fill/submit. Use this for app-specific actions.

Sound effects

tsx
<AvatarWidget
  agentId="agt_abc123"
  soundEffects={{
    chime: true,         // navigate sound
    confirmation: true,  // fill / submit sound
    thinking: false,     // optional thinking loop
  }}
/>
NameTypeDescription
soundEffectsSoundEffectsConfigToggle individual sound effects. All default to true; pass { thinking: false } etc. to silence.

Chrome / UI controls

NameTypeDescription
showMinimizebooleanShow the minimize button in expanded layout. Default: true.
showClosebooleanShow the close button. Default: true.
chromelessbooleanHide header + toolbar. Close button still appears. Default: false.
compactControlsbooleanHide the top header and tuck secondary controls under an overflow menu. Default: false.
floatingChromeContainerHTMLElement | nullContainer for portaled floating chrome (side-tab, bottom bar). Default: document.body. Pass a specific container to keep chrome anchored to a card edge instead of the viewport.

Team switching

Render multiple agent personas and let the visitor switch between them — useful for multi-role demos (sales / support / engineering).

tsx
<AvatarWidget
  agentId="agt_abc123"
  teamMembers={[
    { id: "sales",   name: "Sales", agentId: "agt_sales_xyz", avatarImageUrl: "/sales.png" },
    { id: "support", name: "Support", agentId: "agt_support_xyz", avatarImageUrl: "/support.png" },
  ]}
  currentTeamMemberId="sales"
  onTeamMemberChange={(member) => setCurrentMember(member.id)}
/>
NameTypeDescription
teamMembersTeamMember[]List of agents the visitor can switch between.
currentTeamMemberIdstringControlled current team member ID.
onTeamMemberChange(member: TeamMember) => voidCallback fired on switch.

Transformation overlay

Show a spinner overlay during avatar/voice transitions:

tsx
<AvatarWidget
  agentId={agentId}
  transforming={isUpdating}
  transformingLabel="Updating voice…"
/>
NameTypeDescription
transformingbooleanShow the transformation overlay.
transformingLabelstringCaption under the spinner. Default: "Transforming…".

Advanced: Controlled session

For cross-page persistence (one session that survives route changes) or shared rooms (multiple widgets, one session), bring your own session via controlledSession. See Controlled session.

NameTypeDescription
controlledSessionControlledSessionConsumer-owned session. When set, the widget does not create its own LiveKit room. You manage connect/disconnect lifecycle externally.

Styling

NameTypeDescription
classNamestringClass on the wrapper element.
styleCSSPropertiesInline styles on the wrapper.
zIndexnumberz-index for floating chrome. Default: 2147483647 (max safe int) so the widget stacks above modals and toasts.

Imperative methods (ref handle)

The component uses forwardRef and exposes a small imperative API.

tsx
import { useRef } from "react";
import { AvatarWidget, type AvatarWidgetHandle } from "@livelayer/react";

function App() {
  const ref = useRef<AvatarWidgetHandle>(null);

  const sendCustomEvent = async () => {
    await ref.current?.sendData({
      type: "user_action",
      action: "added_to_cart",
      productId: "sku_123",
    });
  };

  return <AvatarWidget ref={ref} agentId="agt_abc123" />;
}
NameTypeDescription
sendData(data: Record<string, unknown>) => Promise<void>Publish a JSON payload over the LiveKit data channel. The agent receives it as a data message. No-op if not connected.