<!-- AvatarFactory Docs · https://avatarfactory.in/docs/sdk/react/widget · Full map: https://avatarfactory.in/llms.txt -->

# AvatarWidget

A floating launcher that expands into a live call panel. One component, no layout work.

`AvatarWidget` is the drop-in site assistant: a bubble docked to a corner that
expands into a panel and starts a call. It is `position: fixed` and sizes itself,
so unlike [`Avatar`](/docs/sdk/react/avatar) it needs no sized parent — you drop it
in and it places itself.

```tsx
import { AvatarProvider, AvatarWidget } from "@avatarfactory/react";
import "@avatarfactory/react/styles.css";

const SiteWidget = () => (
  <AvatarProvider
    config={{
      getSessionToken: async () => fetchSessionToken(),
      mode: "call",
      controls: { enabled: true },
      avatar: { avatarId: "default" },
    }}
  >
    <AvatarWidget title="Support" launcherLabel="Talk to us" />
  </AvatarProvider>
);
```

> Three things the prop list does not tell you. **It renders its own ``**
>   into the panel — do not add one. **It reads the session through `useAvatar()`**,
>   so it only works inside an `AvatarProvider`. And the stylesheet is not optional
>   here: the widget's box is built from CSS custom properties in
>   `@avatarfactory/react/styles.css`, so without that import it has no size at all.

## What the widget costs before it opens

Mounting it fetches the avatar config and the `.riv` file so the bubble can idle
the real character. That is all. `getSessionToken` is not called and **nothing is
billed** until the widget is opened and the session starts.

## Appearance

Every field is optional and defaulted, so `` with no `appearance`
renders correctly.

```tsx
<AvatarWidget
  appearance={{
    position: "bottom-right",
    offset: { x: 24, y: 24 },
    bubbleSize: 72,
    accent: "#10b981",
    draggable: true,
    teaser: { text: "Talk to a live avatar", delayMs: 9000 },
  }}
/>
```

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `appearance.position` | `WidgetPosition` | `"bottom-right"` | Corner the widget docks to: bottom-right, bottom-left, top-right, top-left. Corners only — a launcher does not live in the middle of a page. |
| `appearance.offset` | `{ x: number; y: number }` | `{ x: 20, y: 20 }` | Distance in pixels from the docked corner. |
| `appearance.bubbleSize` | `number` | `64` | Diameter of the collapsed launcher, in pixels. |
| `appearance.panel` | `{ w: number; h: number }` | `{ w: 384, h: 560 }` | Size of the expanded panel, in pixels. Ignored on mobile when mobile is set to sheet. |
| `appearance.accent` | `string` | — | Ring, controls, and focus colour. Falls back to a tone derived from the avatar. |
| `appearance.panelBackground` | `string` | `"#F4F2EE"` | Panel backdrop once expanded. The collapsed launcher stays transparent so the character composites onto your page. Pass a hex colour — the widget picks readable foreground ink from its luminance, and anything that is not hex keeps the dark default. |
| `appearance.avatarScale` | `number` | `0.6` | Fraction of the panel the character fills, 0 to 1. Below 1 it is inset so gestures reaching outside the body are not clipped. |
| `appearance.draggable` | `boolean` | `false` | Let the user drag the widget. Once moved it stops docking to position, and the panel expands from wherever it was dropped. |
| `appearance.launcher` | `WidgetLauncherStyle` | `"avatar"` | avatar idles the real character in the bubble; poster shows a still image; icon shows a generic mark. |
| `appearance.posterUrl` | `string` | — | Shown before the character decodes, and as the entire launcher when launcher is set to poster. |
| `appearance.teaser` | `WidgetTeaser` | — | A silent attract message beside the bubble, shaped { text, delayMs }. Audio before a user gesture is impossible, so attract behaviour is visual only. Default delay is 8000ms. |
| `appearance.mobile` | `WidgetMobileMode` | `"sheet"` | sheet takes over the viewport below 640px; corner keeps the desktop treatment at every width. |
| `appearance.zIndex` | `number` | `2147483000` | Just under the maximum, so a host page can still stack something above it if it must. |

> `AvatarWidgetProps` extends `WidgetConfig`, so `behaviour`, `context`, and
>   `limits` are accepted by the type — but **on the React path the component
>   ignores them**. They belong to the hosted embed, where a deployment document
>   supplies them. In React, their equivalents go on the provider config instead:
>   `behaviour.greeting` becomes [`avatar.greeting`](/docs/sdk/react/greeting-ringback),
>   `behaviour.connectTone` becomes `connectTone`, and `behaviour.transcript` becomes
>   [`transcript`](/docs/sdk/react/transcript).

## Component props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` | `string` | — | Heading shown at the top of the expanded panel. Omit it and the panel has no heading — most widgets do not need one. |
| `launcherLabel` | `string` | `"Talk to the assistant"` | Accessible label for the collapsed bubble, read by screen readers. |
| `autoStart` | `boolean` | `true` | Start the session on the same click that opens the panel. See the autoplay note below before turning this off. |
| `open` | `boolean` | — | Controls the panel yourself. Omit it to let the widget own its own open state. |
| `onOpenChange` | `(open: boolean) => void` | — | Fires whenever the panel opens or closes, controlled or not. |
| `onStageChange` | `(stage: WidgetStage) => void` | — | Fires on every stage transition. See Stages below. |
| `onResize` | `(box: WidgetBox) => void` | — | Fires on every box change. The hosted embed loader resizes its iframe from this; in a React app you rarely need it. |
| `composer` | `React.ReactNode` | — | Your own affordance rendered at the foot of the panel — a text input for tts mode, say. |
| `usePictureInPicture` | `boolean` | — | Camera on the stage, avatar in the corner. Only honoured while expanded — the collapsed bubble is too small for two views. |
| `className` | `string` | — | Applied to the widget root. |
| `style` | `React.CSSProperties` | — | Applied to the widget root. |

> **`autoStart` must ride the opening click.** Browsers only unlock audio inside a
>   real user gesture. The widget starts the session in the same task as the launcher
>   click, which is what makes the avatar audible. If you turn `autoStart` off and
>   call `start()` later from an effect or a timer, the session will connect and the
>   avatar will stay **mute**.

## Stages

`onStageChange` reports where the widget is. This is not session state — a widget
can be `expanded` with no session at all, and a call survives the panel being
collapsed.

| Stage | Meaning |
| --- | --- |
| dormant | Collapsed to the bubble, no session. |
| expanded | Panel open, no session running. |
| connecting | Panel open, session starting. |
| live | Call in progress. The launcher shows an elapsed clock. |
| ended | The server sent a notice (limit, plan, or test-key expiry) and the call is closing. |

> `WidgetStage` also declares `"expanding"`, but the React widget never emits it —
>   it is reserved for the hosted embed. Do not write logic that waits for it.

When a call ends, an expanded panel folds itself back to the bubble. An open panel
with a dead session is just a dead box.

## Controlling it yourself

`useAvatarWidget()` gives you the widget's presentation state and controls from
anywhere inside the provider. Session control stays on
[`useAvatar()`](/docs/sdk/react/use-avatar).

```tsx
import { useAvatarWidget } from "@avatarfactory/react";

function HelpButton() {
  const { openAndStart, isOpen, stage } = useAvatarWidget();

  // A real click handler — this is what unlocks audio.
  return (
    <button onClick={openAndStart} disabled={isOpen}>
      {stage === "live" ? "Call in progress" : "Ask for help"}
    </button>
  );
}
```

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `isOpen` | `boolean` | — | Panel expanded. Independent of the session. |
| `stage` | `WidgetStage` | — | The current stage, the same value onStageChange receives. |
| `open` | `() => void` | — | Expand the panel without starting a session. |
| `close` | `() => void` | — | Collapse the panel. Does not end a running call. |
| `toggle` | `() => void` | — | Flip the panel open or closed. |
| `openAndStart` | `() => void` | — | Expand and connect in one task. Call it from a real click handler, or the session comes up mute. |

To hold the open state in your own component instead, pass `open` and
`onOpenChange` and the widget becomes fully controlled.

```tsx
const [open, setOpen] = useState(false);

<AvatarWidget open={open} onOpenChange={setOpen} />
```

## Not available on React Native

The widget is a web-only component — it depends on `position: fixed`, viewport
geometry, and pointer dragging. `@avatarfactory/react-native` exports no widget.
On mobile, render [`Avatar`](/docs/sdk/react-native/avatar) inside your own screen
or modal and drive it with `useAvatar()`.

## Types

```tsx
type WidgetPosition = "bottom-right" | "bottom-left" | "top-right" | "top-left";
type WidgetLauncherStyle = "avatar" | "poster" | "icon";
type WidgetMobileMode = "sheet" | "corner";
type WidgetStage =
  | "dormant" | "expanding" | "expanded"
  | "connecting" | "live" | "ended";

type WidgetTeaser = { text: string; delayMs?: number };
type WidgetPanelSize = { w: number; h: number };
type WidgetOffset = { x: number; y: number };
```

The defaults are exported, so you can read them rather than copying the numbers:

```tsx
import { WIDGET_DEFAULTS, resolveWidgetAppearance } from "@avatarfactory/react";

// Fold your partial appearance over the defaults exactly as the widget does.
const resolved = resolveWidgetAppearance({ bubbleSize: 72 });
```
