Botonom Avatar
Add an AI-powered animated avatar to any website with just two lines of HTML. No iframe — the avatar renders as SVG directly in your DOM.
This guide begins with Overview, continues through Quick Start, HTML Attributes Reference, Layout Options, and finishes with JavaScript API.
Overview
Botonom Avatar is a lightweight embed widget that renders AI-powered animated SVG avatars on any web page. It loads via a single <script> tag and renders directly into your DOM — no iframe, no CORS issues.
What it can do
| Capability | Description |
|---|---|
| Animated expressions | Happy, sad, thinking, listening, winking — triggered via JavaScript API |
| Talking animation | Lip-sync animation with manual mouth scale control (0.0–1.2) |
| Eye tracking | Avatar eyes follow the visitor's mouse cursor |
| Multiple layouts | Portrait (2:3), landscape (3:2), and profile (1:1) aspect ratios |
| Multi-widget | Multiple avatars on one page, each controlled independently by UUID |
| SPA-ready | autoInit() re-scans the DOM after client-side navigation |
| Lightweight | ~80 KB gzipped, loaded once and cached |
AgentsPage), agent detail pages, dashboard settings, and the overview insight bubbles. The React wrapper is BotonomEmbed.tsx.Quick Start
Two lines of HTML — that's it:
<div
data-botonom-avatar-uuid="YOUR-AVATAR-UUID"
data-botonom-avatar-layout="portrait"
style="width: 360px; height: 540px;"
></div>
<script src="https://api.botonom.com/assets/js/widgets/embed.js" async></script>
The script automatically finds every <div> with a data-botonom-avatar-uuid attribute, fetches the avatar configuration from the API, and renders the animated SVG directly into the container.
https://api.botonom.com/assets/js/widgets/embed.js. Always load it with the async attribute to avoid blocking page rendering.HTML Attributes Reference
Every avatar container is configured via data- attributes on the host <div>:
| Attribute | Required | Default | Description |
|---|---|---|---|
data-botonom-avatar-uuid | Yes | — | Your avatar UUID from the Botonom Dashboard |
data-botonom-avatar-layout | No | portrait | portrait, landscape, or profile |
data-botonom-avatar-api | No | https://api.botonom.com | Override API base URL (for staging or self-hosted) |
The script discovers all matching <div> elements on page load. For SPAs, call BotonomAvatar.autoInit() after DOM updates to re-scan.
Layout Options
Set data-botonom-avatar-layout to control the avatar's aspect ratio and rendering style:
| Layout | Aspect Ratio | Best For | Recommended Size |
|---|---|---|---|
portrait | 2 : 3 | Sidebars, cards, chatbots | 360 × 540 |
landscape | 3 : 2 | Banners, headers | 440 × 300 |
profile | 1 : 1 | Profile pictures, icons | 200 × 200 |
Examples
Portrait (default)
<div data-botonom-avatar-uuid="UUID"
data-botonom-avatar-layout="portrait"
style="width: 360px; height: 540px;"></div>
Landscape Banner
<div data-botonom-avatar-uuid="UUID"
data-botonom-avatar-layout="landscape"
style="width: 100%; max-width: 600px; height: 300px;"></div>
Profile Icon (circular)
<div data-botonom-avatar-uuid="UUID"
data-botonom-avatar-layout="profile"
style="width: 120px; height: 120px; border-radius: 50%; overflow: hidden;"></div>
border-radius: 50% and overflow: hidden on the container <div>.JavaScript API
Control the avatar's expressions and animations programmatically after the widget loads.
Global Shorthand (Single Widget)
When there's only one avatar on the page, use the global BotonomAvatar object directly:
// Expressions
BotonomAvatar.happy(); // Smiling — hands on cheeks
BotonomAvatar.sad(); // Sad — wiping a tear
BotonomAvatar.think(); // Thinking — hand on chin
BotonomAvatar.listen(); // Listening — hand on ear
BotonomAvatar.wink(); // Playful wink
// Talking
BotonomAvatar.talk(); // Start talking animation
BotonomAvatar.stopTalking(); // Stop talking
// Advanced
BotonomAvatar.setMouthScale(0.8); // Manual lip-sync (0.0 – 1.2)
BotonomAvatar.followMouse(); // Eyes follow cursor
// Reset
BotonomAvatar.reset(); // Return to idle
Multi-Widget (by UUID)
When multiple avatars are on the same page, target a specific one:
const avatar = BotonomAvatar.get("YOUR-UUID");
avatar.happy();
avatar.think();
avatar.reset();
Deactivating Expressions
Pass false to deactivate any expression:
BotonomAvatar.happy(false); // Stop smiling
BotonomAvatar.think(false); // Stop thinking
setTimeout or check that BotonomAvatar.get(uuid) is not null before calling expressions.SPA Integration (React / Next.js)
In single-page applications the embed script needs to re-scan the DOM after navigation. Call BotonomAvatar.autoInit() after the widget <div> is mounted.
React Component Pattern
import { useEffect } from "react";
function AvatarWidget({ uuid }: { uuid: string }) {
useEffect(() => {
const existing = document.querySelector(
'script[src="https://api.botonom.com/assets/js/widgets/embed.js"]'
);
if (existing) {
window.BotonomAvatar?.autoInit();
return;
}
const script = document.createElement("script");
script.src = "https://api.botonom.com/assets/js/widgets/embed.js";
script.async = true;
script.onload = () => window.BotonomAvatar?.autoInit();
document.body.appendChild(script);
return () => { document.body.removeChild(script); };
}, []);
return (
<div
data-botonom-avatar-uuid={uuid}
data-botonom-avatar-layout="portrait"
style={{ width: 360, height: 540 }}
/>
);
}
Dynamically Added Widgets
After inserting new avatar <div> elements into the DOM, call autoInit() to discover them:
var avatarHtml = '<div data-botonom-avatar-uuid="NEW-UUID" '
+ 'data-botonom-avatar-layout="profile" '
+ 'style="width: 100px; height: 100px;"></div>';
container.innerHTML = avatarHtml;
BotonomAvatar.autoInit();
BotonomEmbed.tsx — the React wrapper component. It loads the script once globally, calls autoInit() on subsequent mounts, and uses a MutationObserver to detect when the SVG has been injected.Chatbot / AI Assistant Integration
Combine the avatar with a chat interface to create an expressive AI assistant:
<div style="display: flex; gap: 16px;">
<div data-botonom-avatar-uuid="UUID"
data-botonom-avatar-layout="profile"
style="width: 80px; height: 80px; border-radius: 50%;
overflow: hidden; flex-shrink: 0;"></div>
<div id="messages" style="flex: 1;"></div>
</div>
<script src="https://api.botonom.com/assets/js/widgets/embed.js" async></script>
// When AI starts processing
function onAIThinking() {
BotonomAvatar.think();
}
// When AI starts responding
function onAIResponding() {
BotonomAvatar.talk();
}
// When AI finishes
function onAIDone() {
BotonomAvatar.stopTalking();
BotonomAvatar.happy();
setTimeout(() => BotonomAvatar.reset(), 2000);
}
// When an error occurs
function onAIError() {
BotonomAvatar.sad();
}
This pattern maps the AI assistant's state transitions directly to avatar expressions — creating a lifelike conversational experience.
Architecture
The embed widget uses a two-stage loading approach:
Your Website
┌───────────────────────────────────────────────┐
│ <script src="embed.js"> │
│ ↓ │
│ embed.js loads widget.bundle.js (async) │
│ ↓ │
│ <div data-botonom-avatar-uuid="abc123"> │
│ └── <svg> Avatar SVG rendered here </svg> │
│ </div> │
│ │
│ BotonomAvatar.happy() → direct function call │
└───────────────────────────────────────────────┘
│ fetch (config JSON)
▼
api.botonom.com/tr/api/assets/avatar/{uuid}.json
Key design decisions
- No iframe — the avatar renders directly in your page's DOM, inheriting your CSS context
- No CORS issues — the bundle runs in your page context as a regular script
- Lightweight — ~80 KB gzipped for the full widget bundle, loaded once and cached
- Config endpoint — avatar configuration (colors, features, personality) is fetched as a small JSON from a public CDN endpoint:
api.botonom.com/tr/api/assets/avatar/{uuid}.json(no auth required)
React Wrapper — BotonomEmbed.tsx
The Botonom platform ships a production-ready React wrapper at /src/app/components/BotonomEmbed.tsx.
Exports
| Export | Type | Description |
|---|---|---|
BotonomEmbed | Component | Default export. Renders the embed <div>, auto-loads embed.js, shows a spinner until SVG injection is detected, hover triggers .happy() / .reset() |
useBotonomWidget(uuid) | Hook | Returns BotonomWidgetInstance | null. Tries BotonomAvatar.get(uuid) on mount + 1.5s retry |
AGENT_EMBED_UUIDS | Record | Legacy UUID map — deprecated in favor of agent-catalog.ts → embedUuid |
BotonomWidgetInstance | Interface | TypeScript contract for all expression/animation methods |
Props
interface BotonomEmbedProps {
uuid: string;
layout?: "portrait" | "landscape" | "profile"; // default: "portrait"
className?: string;
style?: React.CSSProperties;
}
Behavior
- Script is loaded once globally; subsequent mounts call
autoInit()to re-scan MutationObserverwatches for<svg>child injection to hide the loading spinner- Fallback: spinner auto-hides after 6 seconds even if widget bundle fails to load
- Hover interaction:
mouseenter→BotonomAvatar.get(uuid).happy(),mouseleave→.reset()
Consumers
| Component | Usage |
|---|---|
AgentsPage.tsx | Agent card avatars (portrait layout) |
AgentDetailPage.tsx | Full-size agent profile avatar |
SettingsPage.tsx | Agent management cards (portrait, h-200px) |
OverviewPage.tsx | AgentInsightBubble mini avatars |
Troubleshooting
Common issues and solutions:
| Issue | Solution |
|---|---|
| Avatar not appearing | Verify data-botonom-avatar-uuid is set and contains a valid UUID |
| Widget blank after SPA navigation | Call BotonomAvatar.autoInit() after the DOM update |
.happy() not working | Wait for the widget to fully load; check BotonomAvatar.get(uuid) is not null |
| Multiple avatars on one page | Each <div> needs a unique UUID; use BotonomAvatar.get("uuid") to control each |
| Sandbox / CORS block | embed.js won't load in restricted environments (e.g. Figma Make sandbox); BotonomEmbed.tsx gracefully falls back to a spinner/gradient placeholder |
| Avatar flickering on re-render | Ensure the uuid prop is stable (not re-created on every render) |
embed.js script may be blocked. The BotonomEmbed component handles this gracefully with a loading fallback that auto-hides after 6 seconds.Live Demo
Interactive preview of the Botonom Avatar widget. Hover over the avatar to trigger the .happy() expression. Try switching between layout modes:
a1b2c3d4-e5f6-7890-abcd-ef1234567890· Personal Assistant