Browser support
Supported browsers and fallbacks.
Last updated
Supported browsers#
Baseline support matches modern evergreen browsers:
Chromium-based browsers (Brave, Vivaldi, Arc, DuckDuckGo Browser) work if their underlying Chromium version is ≥ 100.
Explicitly not supported#
- Internet Explorer (any version).
- Legacy Edge (pre-Chromium, EdgeHTML).
- Opera Mini (proxy-rendered, no JS).
- UC Browser (ancient WebKit fork).
If you need to support IE or similar, the widget won't render. Detect and show a fallback contact form:
const supportsWidget = 'attachShadow' in Element.prototype && 'fetch' in window && typeof ReadableStream === 'function';
if (!supportsWidget) {
document.getElementById('legacy-contact').style.display = 'block';
}Features used#
The widget relies on:
Features marked "Feature-detected" gracefully degrade when unavailable
— e.g., on iOS 17.3 and below, navigator.vibrate is missing, so
haptics silently no-op.
Mobile specifics#
iOS Safari#
- Full support on iOS 15+.
- Haptics — WebKit doesn't expose
navigator.vibrate. We use a programmatic<input type="checkbox" switch>click on iOS 17.4+ to trigger a single haptic tick. Custom patterns flatten to one tick. - Voice (local) —
SpeechRecognitionis available but quality varies. Recommendfeatures.voice: "server"for production. - Keyboard avoidance — when the on-screen keyboard appears, the panel auto-adjusts to stay visible.
- Text-entry focus — the composer, Help search, and form/HITL fields keep a minimum 16px computed font on touch devices so focusing them does not trigger iOS page zoom. Each uses its existing border as the single focus indicator.
- Viewport units — we use
dvhwhere supported (iOS 16+) and fall back tovhbelow.
Android Chrome#
- Full support.
- Haptics —
navigator.vibratesupports both single and pattern vibrations. - Voice (local) — Web Speech works well.
Samsung Internet + other Android browsers#
- Samsung Internet 17+ — Chromium-based, full support.
- Firefox Android — tested and working.
- Brave / DuckDuckGo — Chromium-based, work identically to Chrome.
Accessibility#
Keyboard navigation#
- Tab rotates through all interactive elements (launcher, header buttons, menu items, textarea, send, history rows).
- Shift+Tab reverses.
- Enter / Space on the launcher opens the panel.
- Escape closes the panel (or exits fullscreen in inline mode).
- Arrow Up / Down navigates within popover menus.
- Home / End jump to first / last menu item.
- Enter on a menu item selects.
Screen readers#
- Every icon-only button has an
aria-label. - Popover menus use
role="menu"+role="menuitem"with properaria-haspopup+aria-expanded. - Switch-style actions carry
aria-pressed. - Active language / theme options use
aria-current="true". - The panel is a
role="dialog"witharia-modal="false"(non-blocking) and focus trap.
prefers-reduced-motion#
When the user has motion reduced:
- Launcher callout animations disabled.
- Panel open/close animations shortened.
- Haptic feedback disabled.
- Sound volume halved.
prefers-color-scheme#
The default theme mode is "auto" — follows the OS preference.
Users can override via the in-widget theme picker.
Touch vs pointer#
The widget detects touch capability via
matchMedia("(hover: none) and (pointer: coarse)") and enables
haptics only on touch devices. Desktop users with mice never get
vibration prompts.
Feature-detection code#
If you want to check for widget compatibility before installing:
function helpaiSupported() {
if (typeof window === 'undefined') return false;
if (!('customElements' in window)) return false;
if (!('attachShadow' in Element.prototype)) return false;
if (!('fetch' in window)) return false;
if (typeof ReadableStream !== 'function') return false;
if (typeof Promise !== 'function') return false;
return true;
}
if (helpaiSupported()) {
// Load the widget
const s = document.createElement('script');
s.src = 'https://cdn.help.ai/sdk/js/elements/latest/elements.js';
s.setAttribute('data-public-key', 'agent_pk_...');
s.setAttribute('data-ai-agent-deployment-id', '01900000-0000-7000-8000-000000000000');
s.defer = true;
document.head.appendChild(s);
} else {
// Show fallback
document.getElementById('legacy-chat-fallback').style.display = 'block';
}Polyfills#
We don't ship any — the feature set is chosen so that every supported browser has native implementations.
If you need to support an older browser via your own polyfills
(core-js, whatwg-fetch, etc.), the widget should work, but we
don't test this path. File an issue if something breaks and we'll
look.
Performance targets#
On a mid-tier laptop (M1 MacBook Air, 2020):
On a 2019 Android mid-range (Galaxy A30):
We run Lighthouse audits on every release; the widget never regresses the host page's performance score by more than 2 points.
Degraded modes#
Offline#
- If the bundle is cached, the widget renders.
- Attempting to send fires an
errorevent. Listen to provide a fallback:tsinit({ onError: (err) => { if (!navigator.onLine) { alert('You appear to be offline. Please try again later.'); } }, }); - Cached chats remain viewable.
- When the connection returns, the widget continues working automatically.
Slow networks#
- The streaming pipeline is resilient to per-frame delays.
- If the stream gets stuck, the user sees a "thinking…" indicator.
- User can click Stop to abort and retry.
No localStorage (private mode on some browsers)#
- The widget falls back to a no-op storage adapter (writes succeed but don't persist).
- Chat history doesn't persist across reloads.
- Everything else works.
Old browsers#
- The widget shows nothing (custom elements / shadow DOM aren't available). Your page renders normally; the widget is just absent.
- Feature-detect if you want to show a fallback.
Device form factors#
Tested on:
- Desktop (1280×720 minimum — smaller works but cramped in floating mode).
- Tablet (iPad landscape/portrait, Android tablets).
- Mobile (360×640 minimum — iPhone SE, Android Pixel).
- Foldable (Galaxy Z Fold — adapts via
matchMedia).
Responsive behavior:
initialSize: "auto"expands the panel on wide viewports (≥ autoSizeBreakpoint, default 900px).- Below the breakpoint, the panel defaults to a phone-friendly compact size.
Known limitations#
- iOS Safari viewport units — we use
dvhwhen supported (iOS 16+). On older iOS, use100vhfallback may over-expand by the browser chrome height. - Android haptics vary by device — OEM customizations (Samsung,
Xiaomi) sometimes override or weaken
navigator.vibrate. Not much we can do. - Firefox streaming markdown — Firefox's
streaming-markdownruns ~5% slower than Chromium on long streams. Imperceptible unless streaming tens of thousands of tokens. - Dark reader extensions — "Dark Reader" (the popular dark-mode browser extension) can invert the widget's colors even in dark mode. Workaround: users disable Dark Reader for your site.
Reporting compatibility issues#
Found a browser where the widget misbehaves? Please file an issue with:
- Browser name + version.
- OS + version.
- Device type (desktop / mobile / tablet).
- Steps to reproduce.
- Screenshot or screen recording.
- Console output.