Skip to content

Browser support

Supported browsers and fallbacks.

Last updated

Supported browsers#

Baseline support matches modern evergreen browsers:

BrowserMinimum versionNotes
Chrome / Edge100Released April 2022
Firefox100Released May 2022
Safari15macOS Big Sur + / iOS 15 +
Opera86Chromium-based
Samsung Internet17Android

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:

js
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:

APIFeature detectionWhy
Shadow DOM (attachShadow)'attachShadow' in Element.prototypeWidget rendering isolation
Custom Elementstypeof customElements<web-ai-chat> element
Constructable Stylesheets'adoptedStyleSheets' in ShadowRoot.prototypeEfficient CSS adoption (falls back to <style>)
ES ModulesNativeNPM + web-component sub-entry
fetchNativeAPI calls
ReadableStreamNativeStreaming responses
TextDecoder / TextEncoderNativeSSE frame parsing
Async generators (for await)NativeStream consumers
CSS custom properties:root { --var: ... }Theming
CSS contain: sizeSupported everywhere we supportInline-mode layout containment
IntersectionObserverNativeAuto-scroll behaviour
matchMediaNativeprefers-color-scheme, prefers-reduced-motion, touch detection
MediaRecorderFeature-detectedVoice input (server mode)
Web Speech APIFeature-detectedVoice input (local mode)
navigator.vibrateFeature-detectedHaptic feedback (Android)
AudioContextFeature-detectedSound effects

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) — SpeechRecognition is available but quality varies. Recommend features.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 dvh where supported (iOS 16+) and fall back to vh below.

Android Chrome#

  • Full support.
  • Haptics — navigator.vibrate supports 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 proper aria-haspopup + aria-expanded.
  • Switch-style actions carry aria-pressed.
  • Active language / theme options use aria-current="true".
  • The panel is a role="dialog" with aria-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:

js
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):

MetricTargetMeasured (current)
Bundle download (gzipped)< 110 KB~107 KB
First byte to first bubble painted< 200ms~150ms
Time to interactive (first open)< 1s~600ms
Per-token stream render latency< 5ms~2ms
Panel close → FAB visible transition< 200ms~180ms

On a 2019 Android mid-range (Galaxy A30):

MetricTargetMeasured
First byte to first bubble painted< 500ms~350ms
Time to interactive (first open)< 2s~1.1s

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 error event. Listen to provide a fallback:
    ts
    init({
      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 dvh when supported (iOS 16+). On older iOS, use 100vh fallback 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-markdown runs ~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.

github.com/help-ai/elements/issues (opens another site)

Still need help?

Ask the community