TryonAR

Embed loader

Two lines of HTML and your customers can try on a product from any browser. The loader registers a <tryonar-button> custom element that opens a fullscreen try-on iframe; an event surface lets you wire the rest of your site.

Quickstart

Paste these two snippets anywhere in your page’s HTML. That’s the whole install.

<script type="module"
  src="https://cdn.tryonar.net/widget.js"
  defer></script>

<tryonar-button product-id="prod_YOUR_PRODUCT_ID"></tryonar-button>

The button renders inline where you put the element, inheriting your page’s font. Click it: a fullscreen modal opens with the try-on for your product.

The script is ~13 KB gzipped, ships from a CDN with stale-while-revalidate caching, and re-entrant (including it twice is a no-op).

Configuration

Every option is a plain HTML attribute on the <tryonar-button> element. Set them in your markup; updates take effect on the next render.

AttributeTypeDefaultPurpose
product-idstring (required)TryonAR product ID, format prod_…
labelstringTry on & find my shadeButton text override
modeinline | floatinginlineInline = render where the element is. Floating = fixed to a corner.
floating-positionbottom-right | bottom-leftbottom-rightOnly used when mode=floating
themeauto | light | darkautoAuto follows prefers-color-scheme
accent-colorhex stringOverride the button background with your brand color
debugbooleanfalseLogs click + lifecycle to the console

Events

Subscribe via window.TryonAR.on(name, handler) and unsubscribe with .off(name, handler). Payloads are typed; see the table below for the shape of each.

window.TryonAR.on('cart.add', ({ productId, variantId }) => {
  // wire your analytics or your own cart logic here
});
EventPayloadWhen it fires
button.click{ productId }Before the modal opens. Cancel via the DOM tryonar:open CustomEvent.
modal.open{ productId }Modal mounted; iframe loading.
widget.ready{ productId, protocolVersion }Iframe handshake completed; widget UI is interactive.
cart.add{ productId, variantId?, size?, metadata? }Add-to-cart succeeded via at least one bridge.
cart.error{ productId, message }Add-to-cart failed across every applicable bridge.
iframe.error{ productId, code, message }Iframe load failed or widget posted an error.
iframe.analytics{ event, props? }Forwarded analytics from inside the widget (e.g. shade swaps).
modal.close{ productId }Any close path: ✕, ESC, backdrop click, browser back, cart-add success.

Cart bridges

When the user taps “Add to bag” inside the try-on, the loader runs two bridges in order:

Shopify

Detected automatically when window.Shopify.shop exists (every standard Shopify storefront theme). The loader POSTs to /cart/add.js with the variant ID resolved from the metadata.shopifyVariantId field on the TryonAR variant. On a 422 (out-of-stock, etc.) the modal stays open and shows the merchant-facing error inline.

Hydrogen / headless stores don’t expose window.Shopify; the universal CustomEvent path below still fires and you can wire your own cart there.

Universal CustomEvent

Always fires after the platform-specific bridges run, so analytics consumers see every add-to-cart attempt regardless of platform. Listen on window:

window.addEventListener('tryonar:addtocart', (e) => {
  const { productId, variantId, size, metadata } = e.detail;
  // wire to your backend cart
});

Framework notes

Plain HTML

See Quickstart above.

React

React 18+ supports custom-element attributes natively. Cast as any on the JSX element if your TS config doesn’t know about tryonar-button yet.

import Script from 'next/script';

export function TryOnButton({ productId }: { productId: string }) {
  return (
    <>
      <Script src="https://cdn.tryonar.net/widget.js" strategy="afterInteractive" />
      {/* @ts-expect-error - custom element is registered at runtime */}
      <tryonar-button product-id={productId} />
    </>
  );
}

Vue 3

Add tryonar-button to your Vue compiler’s isCustomElement list:

// vite.config.ts
export default defineConfig({
  plugins: [vue({
    template: { compilerOptions: { isCustomElement: tag => tag === 'tryonar-button' } }
  })],
});
<template>
  <tryonar-button :product-id="productId" />
</template>

Astro

Drop the <script> in the layout’s <head> with is:inline. The custom element works in any template.

Versioning

Two URL forms:

  • Stable: https://cdn.tryonar.net/widget.js — what most merchants use. Edge-cached for 5 minutes; we can push fixes fast.
  • Pinned: https://cdn.tryonar.net/v/<version>/widget.js — immutable, cached forever. Use this if you have a strict change-management process; subscribe to our release notes for when to bump.

The protocol version (host ↔ iframe) is independent of the loader version and follows semver. Major bumps are documented in release notes; minor bumps are additive.

Troubleshooting

The button renders but the modal won’t open

Check the browser console for a tryonar:open event being cancelled. If a host-page handler calls preventDefault(), the modal won’t mount. Drop debug on the button to see the click hit the loader.

Modal opens but spinner never clears

After 2 seconds the iframe will retry the handshake; after another 2 seconds it shows a “Connection lost” screen. The most common cause is a Content-Security-Policy that blocks the iframe’s origin — add frame-src https://try.tryonar.net to your CSP.

Add-to-cart doesn’t hit my cart

On Shopify: confirm window.Shopify.shop is set (it is on standard themes; missing on Hydrogen and most headless stacks). On any platform, listen for the universal tryonar:addtocart CustomEvent on window — it always fires.

The button looks wrong on my page

The button lives in shadow DOM; your CSS doesn’t leak into it. Customize via the theme and accent-color attributes — full theming is on the roadmap.