One iframe, positioned over your element
Libra.init() creates a fixed-position wrapper (div[data-libra-sdk]) containing an iframe, and appends it to document.body (or to your mountTarget). The iframe loads from a blob: URL with the entire chat application and its styles inlined, so:
- Zero external requests at load time. The bundle is self-contained.
- Full CSS isolation. Host styles cannot affect the widget and vice versa.
- Host-controlled CSP. The blob iframe is generated by your page and inherits your origin. Because of Safari-specific CSP enforcement, SDK-enabled pages must allow same-origin
blob:frames; see Security headers.
Libra.attach(elementOrId) does not move the iframe in the DOM. It only updates the wrapper’s CSS position, continuously synced to the target’s bounding box with requestAnimationFrame, so it follows scrolling, layout shifts and animations. That is why the chat session, scroll position and in-progress streaming are preserved across detach() / attach(), even when you attach to a different container after an SPA route change.
Lifecycle
Call
init() once at app startup, after document.body exists. Set mountTarget at init only: an iframe cannot be reparented later without the browser reloading it, which would destroy the session.
Who owns the header
The SDK renders its Libra headbar (wordmark and close button) only when you passonClose. Clicking close calls your callback; the host owns the close behaviour, typically hiding the product-owned wrapper (a side panel or modal) and calling Libra.detach().
If your product already renders a header or close control around the widget, omit onClose and call Libra.detach() from your own control. Header ownership covers only this outer bar; the authenticated chat screen still renders its own controls.
Authentication in one paragraph
By default the SDK handles authentication itself through an Auth0 popup, with Sign in and Create account (onboarding runs inside the iframe). Tokens live in the iframe’slocalStorage, namespaced under libra: so they never collide with your keys, and later loads skip the auth screen while a valid token exists. No auth configuration is needed from the host. Products that must not let the browser hold a Libra token use proxy mode instead, where your BFF exchanges the user’s product token server-side.
Storage and messaging
The blob iframe inherits your page’s origin, so SDK state — the auth token, the locale, the active chat id — is written into your own origin’slocalStorage, namespaced under a libra: prefix. The isolation is namespacing, not a separate storage partition. Three consequences worth knowing:
libra:keys show up in your storage inspector.- Clearing site data signs the user out.
- A host that calls
localStorage.clear()wipes the SDK session.
postMessage, not an inline script, so the iframe HTML is static and CSP hashes remain stable across config changes.
Multi-page hosts
On a fresh page load the SDK reopens the user’s last active chat automatically, from a per-user key in that same storage. It is cleared when the user starts a new chat, and deliberately kept across sign-out so the same user gets their session back when they sign in again. Research-source selections are preserved too. The one thing your page must re-supply on every navigation is the document suggestion —setDocumentSuggestion() state is in-memory only and does not survive a page load. See Document suggestions.
Next: Embedding guide
Containers, stacking context, and the constraints on
mountTarget.
