Chat Widget: Embedding, Customization & Control
Guide to embedding the chat widget and customizing its appearance and behavior. Covers script tag options (data-launcher, data-greeting, data-open), Shadow DOM style isolation, CSS variables and ::part() selectors, plus the window.uHamkor JavaScript API — methods, state snapshot, events and practical recipes. Accessibility, print and reduced-motion support included.
🎨 Chat Widget: Embedding, Customization & Control
The widget is added to a site with a single <script> tag, isolates its styles with Shadow DOM, and is controlled through CSS custom properties, ::part() selectors and the window.uHamkor JavaScript API.
🚀 Installation
<script
src="https://your-widget-host.com/widget.js"
data-project-uuid="YOUR_PROJECT_UUID"
data-language="uz"
defer
></script>
Put the tag inside the page <head>. The widget only initializes on domains listed in the project domains setting.
⚙️ Script tag options
data-project-uuid— required. The project the widget belongs to.data-language—uz/ru/en. Default:uz.data-user— external user id (identifies the visitor).data-launcher—true/false. Default:true. Renders the floating launcher button.data-greeting—true/false. Default:true. Shows the greeting bubble.data-open—true/false. Default:false. Opens the chat window right after mount.
data-language, data-user, data-launcher and data-greeting are watched at runtime — changing the attribute updates the widget immediately.
🎯 CSS Variables
Override these variables on the .chat-widget class:
.chat-widget {
/* Colors (HSL format without hsl()) */
--cw-primary: 221 83% 53%;
--cw-secondary: 258 83% 66%;
--cw-background: 0 0% 100%;
--cw-foreground: 222.2 84% 4.9%;
/* Layout */
--cw-z-index: 50;
--cw-button-bottom: 20px;
--cw-button-right: 20px;
--cw-window-bottom: 20px;
--cw-window-right: 20px;
/* Border radius */
--cw-radius: 1rem;
}
The chat window height is derived from --cw-window-bottom, so raising the widget keeps the window inside the viewport.
🧩 ::part() Selectors
Style internal elements:
.chat-widget::part(button) {}
.chat-widget::part(window) {}
.chat-widget::part(header) {}
.chat-widget::part(body) {}
.chat-widget::part(footer) {}
🖨️ Print Behavior
The widget hides automatically during printing. No extra configuration needed.
🎞️ Reduced Motion
Animations are disabled automatically for users with prefers-reduced-motion: reduce.
💡 Examples
📍 Reposition the widget
.chat-widget {
--cw-button-bottom: 100px;
--cw-button-right: 30px;
--cw-window-bottom: 100px;
--cw-window-right: 30px;
}
🔝 Higher z-index (above modals)
.chat-widget {
--cw-z-index: 99999;
}
🚫 Hide the widget on specific pages
.no-chat .chat-widget {
display: none;
}
🕹️ JavaScript API (window.uHamkor)
The widget publishes a global API on window.uHamkor as soon as it mounts. Use it to drive the widget from your own UI — a header button, a “Contact support” link, an in-app unread badge.
📦 Embed snippet with the command queue
Calls made before widget.js finishes loading are queued and replayed on load, so no setTimeout guessing is needed:
<script>
window.uHamkor = window.uHamkor || function () {
(window.uHamkor.q = window.uHamkor.q || []).push(arguments);
};
uHamkor('hideLauncher'); // host renders its own trigger
</script>
<script
src="https://your-widget-host.com/widget.js"
data-project-uuid="YOUR_PROJECT_UUID"
data-language="uz"
data-launcher="false"
defer
></script>
Both call styles work after load:
uHamkor.open(); // method form
uHamkor('open'); // command form (also used by the queue)
🛠️ Methods
open()/close()/toggle()— control the chat window.isOpen()— window state (boolean).showLauncher()/hideLauncher()— show/hide the floating button. While the chat window is open the button stays visible as the close control.setGreetingEnabled(enabled)— enable/disable the greeting bubble.setLanguage(lang)—en,ru,uz,uz_latn,uz_cyrl.getLanguage()— current language.identify(identity)—{ externalUserId, firstName, lastName, phoneNumber }.logout()— clears the session, token and cached messages.prefill(text)— fills the composer without sending.sendMessage(text)— sends a message; returnsPromise<boolean>(falseif it could not be sent).getUnreadCount()— unread messages.getState()— full state snapshot.subscribe(listener)— fires on every state change; returns an unsubscribe function.on(event, handler)/once/off— event subscription.setPosition({ bottom, right })— moves launcher and window (pixels).setColors(primary, secondary?)— overrides the theme colors at runtime.destroy()— unmounts the widget and removes it from the page.
📊 State snapshot
{
projectUuid: string;
isOpen: boolean;
isLauncherVisible: boolean;
isWidgetEnabled: boolean; // project switched the widget off
unreadCount: number;
language: string;
isAuthorized: boolean;
externalUserId: string | null;
isTyping: boolean; // operator/bot is typing
isOnline: boolean; // support side is online
isBotActive: boolean; // no human operator in the conversation
isAIThinking: boolean;
isResolved: boolean;
callStatus: 'idle' | 'calling' | 'incoming' | 'active' | 'ended' | 'failed';
callType: 'audio' | 'video';
}
📡 Events
ready— widget mounted; payload: snapshot.state— any snapshot field changed.open/close— no payload.unread—{ count }.message—{ direction: 'incoming' | 'outgoing', message }.operator:joined— an operator joined the conversation.chat:resolved— the conversation was resolved.call:incoming/call:started/call:ended—{ type }.language:change—{ language }.identify—{ externalUserId }.destroy— the widget was removed.
Every event is also dispatched on document as uhamkor:<event>, so frameworks can listen without touching the global:
document.addEventListener('uhamkor:unread', e => {
badge.textContent = e.detail.count;
});
🍳 Recipes
🔔 Own trigger button with an unread badge
uHamkor('hideLauncher');
uHamkor('on', 'state', state => {
badge.hidden = state.unreadCount === 0;
badge.textContent = state.unreadCount;
trigger.setAttribute('aria-expanded', String(state.isOpen));
});
trigger.addEventListener('click', () => uHamkor.toggle());
👤 Attach the logged-in user
uHamkor.identify({
externalUserId: user.id,
firstName: user.firstName,
lastName: user.lastName,
phoneNumber: user.phone,
});
💬 Start a conversation from a page action
uHamkor.open();
uHamkor.sendMessage('I need help with order #1234');
🚪 Clear the session on logout
uHamkor.logout();
🔄 Widget behavior
- The floating button can be dragged. When the chat opens it snaps back to its default corner and dragging is disabled, so it stays aligned with the window.
- The button is never hidden while the chat is open — it acts as the close control.
- The greeting bubble is hidden while the chat window is open.
- Open/close animations are staged (header → messages → composer) and are disabled under
prefers-reduced-motion. - The initial greeting message is marked as read, so
unreadCountonly grows on real new messages. setPosition({ bottom })moves the window together with the launcher.
♿ Accessibility
role="dialog"andaria-modal="true"on the chat window.aria-labelon all interactive buttons.aria-haspopup="dialog"on the floating button.- Keyboard navigation support.