uHamkorDocumentation
API Documentation

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-languageuz / ru / en. Default: uz.
  • data-user — external user id (identifies the visitor).
  • data-launchertrue / false. Default: true. Renders the floating launcher button.
  • data-greetingtrue / false. Default: true. Shows the greeting bubble.
  • data-opentrue / 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; returns Promise<boolean> (false if 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 unreadCount only grows on real new messages.
  • setPosition({ bottom }) moves the window together with the launcher.

♿ Accessibility

  • role="dialog" and aria-modal="true" on the chat window.
  • aria-label on all interactive buttons.
  • aria-haspopup="dialog" on the floating button.
  • Keyboard navigation support.