Skip to Content
DocumentationWidgetEmbedding & Placement

What controls what

The widget is configured from two independent places. Knowing which is which saves a lot of guesswork:

What you want to changeWhere it lives
Which agent loads, where it mounts, whether it opens on load, whether the close button showsdata-* attributes on the <script> tag
Corner, colors, fonts, panel size, corner radius, labels, languagesThe character’s appearance config — set in the dashboard, or served from your own URL via data-appearance-url

In short: the script tag decides placement behaviour, the appearance config decides how it looks. There is no data-position or data-color attribute.

The base embed

Paste this before </body> on any page. Everything else on this page is a variation of it.

<script src="https://api.oshara.ai/widget.js" data-agent="support-bot"> </script>

data-agent is the only required attribute — it’s your character’s slug. If it’s missing the widget logs [voice-agent] data-agent (character slug) is required and stops.

The script tag can go anywhere in the page, but its position in the DOM matters for inline mode — see below. For the floating bubble it makes no difference.

Mode 1 — Floating bubble (default)

With no extra attributes the widget mounts fixed on <body>: a circular button in a screen corner that expands into the voice panel. It sits above your page at z-index: 2147483647 and is fully isolated in a shadow DOM, so it can’t inherit or leak your CSS.

The corner comes from the appearance config, not the script tag:

layout.positionResult
"bottom-right"Default. Bubble and panel anchored bottom-right.
"bottom-left"Bottom-left.
"top-right"Top-right.
"top-left"Top-left.

Set it under Characters → [your character] → Appearance → Position, or in appearance JSON:

{ "layout": { "position": "bottom-left" } }

Mode 2 — Inline, inside your own layout

Set data-inline="true" to drop the widget into your page as a normal block of content — a hero section, a sidebar card, a modal, a dedicated /talk-to-us page. The floating bubble is hidden and the panel fills the container you give it.

<div class="voice-embed"> <script src="https://api.oshara.ai/widget.js" data-agent="support-bot" data-inline="true"> </script> </div> <style> .voice-embed { position: relative; /* required — the panel is absolutely positioned */ height: 620px; /* required — the panel fills the container */ width: 100%; max-width: 420px; overflow: hidden; border-radius: 16px; /* your container, your corners */ } </style>

Three rules for inline mode:

  1. The script tag must live inside the container. The widget mounts into the script’s parent element. If your CMS or bundler moves the tag (e.g. hoists it to <head>), inline mounting falls back to <body> and you get a full-page panel.
  2. The container needs position: relative and a real height. The panel is position: absolute; inset: 0 — with a zero-height or statically positioned parent, nothing is visible.
  3. Inline implies open. There’s no bubble to click, so the panel opens on load automatically.

Inline mode ignores panel_width, panel_height, border_radius and layout.position from the appearance config — your container sets the size and corners, and the panel renders square-cornered with no shadow so it sits flush inside it.

Inline is the right choice for a “Talk to us” page or a product-tour section. Use the floating bubble when the agent should be reachable from every page without taking up layout space.

Open the panel on load

<script src="https://api.oshara.ai/widget.js" data-agent="support-bot" data-open-chat="true"> </script>

The panel expands as soon as the widget boots, instead of waiting for a click on the bubble. Useful for landing pages and demos.

data-open-chat must be the literal string "true" or "1". A bare data-open-chat (no value) is not enough — unlike data-close-button-hide, which does accept the bare form.

Hide the close button

<script src="https://api.oshara.ai/widget.js" data-agent="support-bot" data-inline="true" data-close-button-hide> </script>

Removes the ✕ from the panel header. Mostly for inline embeds, where there’s no bubble to collapse back into and a close button would leave an empty hole in your layout. Accepts "true", "1", or the bare attribute. In floating mode the bubble still toggles the panel, so the close button is redundant rather than essential.

Size, shape and colors

All of these come from the appearance config. Defaults shown are what you get if you never touch anything:

Want to changeFieldDefaultNotes
Bubble sizedimensions.fab_size64 pxAlso scales the icon and the label padding.
Bubble shapefab_label""The bubble is always fully rounded. Empty label → circle (icon only); with a label → pill that grows to fit the text.
Panel widthdimensions.panel_width380 pxIgnored inline.
Panel heightdimensions.panel_height620 pxCapped at 100vh - 40px on short screens. Ignored inline.
Panel cornersdimensions.border_radius24 px0 for square corners. Ignored inline (always square).
Corner anchorlayout.position"bottom-right"Ignored inline.
Colorstheme.*Indigo / cyan on whiteEight tokens — see Appearance.
Fontlayout.font_familyInter stackEmpty inherits nothing — the widget is shadow-scoped, so set it explicitly.

A compact square-cornered bubble in the top-left, for example:

{ "dimensions": { "fab_size": 52, "panel_width": 340, "panel_height": 560, "border_radius": 0 }, "layout": { "position": "top-left" } }

A different look per site or page

The dashboard appearance is per character, so every embed of the same agent looks the same. To vary it — a dark panel on your docs, a light one on marketing, a wider panel on desktop-only pages — point data-appearance-url at your own JSON:

<script src="https://api.oshara.ai/widget.js" data-agent="support-bot" data-appearance-url="https://cdn.yoursite.com/widget/dark.json"> </script>
{ "name": "Nova", "fab_label": "Ask Nova", "dimensions": { "fab_size": 72, "panel_width": 420, "border_radius": 8 }, "layout": { "position": "bottom-left" }, "theme": { "background_color": "#0F172A", "text_color": "#F1F5F9", "primary_color": "#818CF8" } }

Notes on the override:

  • The file must be served with CORS headers your page can read (Access-Control-Allow-Origin).
  • Only the keys you include are overridden; everything else falls back to the built-in defaults — not to your dashboard config, which is skipped entirely once you set this URL.
  • The JSON may be bare, or wrapped in data, appearance or widget_appearance — all four shapes are accepted.
  • If the fetch fails the widget logs a warning and boots with default styling rather than breaking.

Tag managers, SPAs and dynamic injection

When a tag manager or bundler makes data-* attributes awkward, set window globals before the script executes:

<script> window.VOICE_AGENT_SLUG = "support-bot"; window.VOICE_OPEN_CHAT = true; window.VOICE_INLINE = false; </script> <script src="https://api.oshara.ai/widget.js" async></script>

Every attribute has a global equivalent — see Configuration. data-* attributes win when both are present.

For single-page apps that mount and unmount the widget on route changes:

// Tear down the current instance — removes the DOM, disconnects audio window.__OsharaVoiceWidget?.destroy();

Re-injecting the script also tears down the previous instance automatically, so a React component that remounts won’t stack up panels. The flip side: only one widget can run per page. Loading a second script tag replaces the first rather than running two agents side by side.

Before it works on your domain

Both the appearance fetch and the call session are gated by the character’s Allowed Origins. In production an unlisted origin — including an empty list — is rejected with 403 Origin not allowed for this agent. Add every domain you embed on under Characters → [your character] → Deploy → Allowed Origins:

https://mysite.com https://staging.mysite.com

Troubleshooting

SymptomCause
Nothing renders; console shows data-agent (character slug) is requiredMissing or misspelled data-agent.
Nothing renders; network shows 403The embedding domain isn’t in Allowed Origins.
Widget appears with default indigo styling and your branding is missingAppearance fetch failed — console logs appearance fetch returned …; using defaults. Check the origin whitelist or your custom appearance URL’s CORS headers.
data-inline set but nothing visibleContainer has no height, isn’t position: relative, or the script tag was hoisted out of it.
Panel opens but the call never connectsMicrophone permission denied, or the page isn’t served over HTTPS (getUserMedia requires a secure context).

Next steps

Last updated on