Widget Installation
Add the Reaktly chat widget to any website — script tag, deferred loader, framework notes, and verification.
Prerequisites
- A Reaktly operator with a widget configuration
- Your operator key (
op_…) — reveal it in the dashboard under the widget's Installation tab (Reveal key) - The domains that may embed the widget, listed under Allowed domains (see Restrict embedding)
1. Embed the widget
Paste this snippet into your site, just before the closing </body> tag:
<script type="module">
import "https://storage.googleapis.com/reaktly-solis-cdn/latest/solis-widget.js";
window.solis("init", {
operatorKey: "op_live_…",
});
</script>The bundle is a side-effecting ES module: importing it registers the global window.solis(...) command. init fetches your widget configuration and renders the launcher.
The dashboard shows the same snippet with your operator key filled in — copy it from the widget's Installation tab.
Optional init options
| Option | Type | Description |
|---|---|---|
operatorKey | string | Required. Your operator's public key (op_…) |
locale | string | Force the widget language, e.g. "de". Omit to let the widget use the visitor's browser language |
contact.userId | string | Your identifier for the visitor — lets the AI and your operators address them consistently |
contact.email | string | Visitor email |
contact.name | string | Visitor name |
showLauncher | boolean | Set false when your own button should open the chat (client-owned trigger) |
panelId | string | id for the widget element, so your own trigger can point at it with aria-controls |
2. Verify the installation
- Load the page in a browser and wait for the launcher to appear in the bottom corner.
- Open the chat and send a message — it should be answered from your knowledge base.
- Watch the browser console: the widget reports a missing or rejected operator key there instead of failing silently.
Deferred loading (recommended for content sites)
The bundle is ~150 kB gzipped. The loader is the small alternative: it installs window.solis as a queue (about 1 kB) and fetches the bundle only when the first visitor actually interacts with the chat.
<script src="https://storage.googleapis.com/reaktly-solis-cdn/latest/solis-loader.umd.js"></script>
<script>
window.solis("init", { operatorKey: "op_live_…" });
</script>
<button type="button" data-solis-toggle aria-expanded="false">Questions?</button>- Any element with a
data-solis-*attribute becomes a trigger — no inline JavaScript, which makes this work in CMS-built headers. - The first click waits for the download. To warm the bundle up in the background instead, add
data-idle="3000"(milliseconds) ordata-preload="eager". - The loader state is observable on the document element:
<html data-solis-loader>isidle,loadingorloaded.
Pin a version in production
latest/ always serves the newest release — convenient while evaluating, but a moving target. For production, pin the bundle and verify its integrity:
<link
rel="stylesheet"
href="https://storage.googleapis.com/reaktly-solis-cdn/v1.26.0/solis-widget.css"
integrity="sha384-…"
crossorigin="anonymous"
/>
<script
type="module"
src="https://storage.googleapis.com/reaktly-solis-cdn/v1.26.0/solis-widget.js"
integrity="sha384-…"
crossorigin="anonymous"
></script>
<script>
window.solis("init", { operatorKey: "op_live_…" });
</script>The hashes come from …/v1.26.0/integrity.json (one sha384 per shipped file). crossorigin="anonymous" is required — without it the browser does not check the hash. A pinned version is never overwritten, so rolling back means pointing at the previous version.
Framework notes
Next.js / React
Import the bundle in a client component and initialise it once:
"use client";
import { useEffect } from "react";
export function ReaktlyWidget({ operatorKey }: { operatorKey: string }) {
useEffect(() => {
let active = true;
import(
"https://storage.googleapis.com/reaktly-solis-cdn/latest/solis-widget.js"
).then(() => {
if (!active) return;
window.solis("init", { operatorKey });
});
return () => {
active = false;
window.solis("destroy");
};
}, [operatorKey]);
return null;
}A React component package is on the roadmap; until it ships, the effect above is the supported integration.
WordPress
Add the snippet from step 1 to your theme's footer.php, or use a "insert headers and footers" plugin. A dedicated Reaktly plugin is on the roadmap.
Plain HTML
The snippet from step 1 is all you need — it works on static sites and any server-rendered stack.
Control the widget from your page
Every command is a window.solis(...) call:
| Command | Effect |
|---|---|
window.solis('open') / ('close') / ('toggle') | Open, close, or toggle the chat window |
window.solis('show') / ('hide') | Show or hide the launcher (e.g. on checkout pages) |
window.solis('update', options) | Update the configuration without a reload |
window.solis('destroy') | Remove the widget and its listeners |
Events
Subscribe with window.solis('on', event, handler) and unsubscribe with off:
| Event | Fires when |
|---|---|
ready | The widget finished initialising |
open / close | The chat window opened or closed |
message:sent / message:received | A message left or arrived |
error | The widget hit an error |
Every event is also dispatched as a DOM event (solis:open, solis:close, …) on window, document, and the widget element — so plain HTML pages can react without the JavaScript API.
Client-owned trigger
When your own button should open the chat, switch off the launcher and drive the widget yourself:
<script type="module">
import "https://storage.googleapis.com/reaktly-solis-cdn/latest/solis-widget.js";
window.solis("init", {
operatorKey: "op_live_…",
showLauncher: false,
panelId: "support-chat",
});
</script>
<button type="button" onclick="window.solis('toggle')">Chat with us</button>Focus is handled for you: the widget remembers what opened it and returns focus there on close. Set panelId if you want aria-controls on your trigger, and reflect aria-expanded from the solis:open / solis:close events.
Restrict embedding
Allowed domains in the widget settings restrict which origins may use your widget (OriginGuard). One pattern per line:
| Pattern | Matches |
|---|---|
example.com | Exact domain |
*.example.com | All subdomains |
https://app.example.com | Specific protocol and domain |
Leaving the list empty allows every domain. Once you list domains, only matching origins are accepted.
Browser support
| Engine | Minimum |
|---|---|
| Chrome / Edge | 111 |
| Safari | 16.2 |
| Firefox | 113 |
The floor is set by color-mix() in the widget's design tokens. There is no transpiled build — below the floor the widget renders nothing rather than breaking the layout.
Troubleshooting
| Symptom | Check |
|---|---|
| Nothing renders | Console for an operator-key error; the key was revealed for the correct operator |
| Widget renders, but requests are rejected | The page's origin is missing from Allowed domains |
| Launcher missing, chat opens via your button | Expected when showLauncher: false |
| Chat opens but stays empty | The operator's widget configuration failed to load — check the network tab for the config request |
Next steps
- Your First Conversation — test the assistant against your content
- Widget Customization — colors, copy, and behaviour
- Knowledge Base Basics — what the AI answers from