Intunix

Installation

Every package wraps the same core engine, so behaviour is identical across them. What differs is how you mount the provider and where the rendered form lives in your tree.

Packages

PackageWhat it gives youPeer requirements
@intunix/reactProvider component, hooks, React-rendered formsreact >= 18, react-dom >= 18
@intunix/vueProvider component or plugin, composables, Vue-rendered formsvue >= 3
@intunix/webDrop-in cx.js, Shadow DOM rendering, window.intunix() queueNone — plain script tag
@intunix/coreIntunixClient and types only. You supply the Renderer.None

React

bash
npm install @intunix/react

@intunix/core comes along as a dependency — you do not install it separately unless you want to import types directly.

Next.js App Router

Root layouts are server components, and are frequently async. The provider uses hooks, so it needs its own client boundary. Create one component that owns the SDK:

components/IntunixClient.tsx
'use client'

import { IntunixProvider } from '@intunix/react'

const API_KEY = process.env.NEXT_PUBLIC_INTUNIX_CLIENT_KEY ?? ''

export function IntunixClientProvider({ children }: { children: React.ReactNode }) {
  // Without a key the SDK cannot load anything — render children untouched
  // rather than initialising a client that will only fail.
  if (!API_KEY) return <>{children}</>

  return <IntunixProvider apiKey={API_KEY}>{children}</IntunixProvider>
}
app/layout.tsx
import { IntunixClientProvider } from '@/components/IntunixClient'

export default async function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <IntunixClientProvider>{children}</IntunixClientProvider>
      </body>
    </html>
  )
}
Do not pass an inline config object

The provider re-runs client.init() whenever its config prop changes identity. An object literal written directly in JSX is a new reference on every render, which re-initialises the SDK in a loop. Hoist it to a module-level constant:

code
// Wrong — new object every render
<IntunixProvider apiKey={key} config={{ register: ['nps_q4'] }} />

// Right — stable reference
const INTUNIX_CONFIG = { register: ['nps_q4'] }
<IntunixProvider apiKey={key} config={INTUNIX_CONFIG} />

Environment variables

In Next.js, NEXT_PUBLIC_* values are inlined at build time, not read at runtime. If you build inside Docker or CI, the key must be passed as a build argument — supplying it only as a runtime environment variable leaves the browser with an empty string.

Dockerfile
ARG NEXT_PUBLIC_INTUNIX_CLIENT_KEY
ENV NEXT_PUBLIC_INTUNIX_CLIENT_KEY=$NEXT_PUBLIC_INTUNIX_CLIENT_KEY
RUN npm run build

Vue 3

bash
npm install @intunix/vue

Component form

Mirrors the React provider: creates the client, provides it to descendants, and mounts an <ExperienceHost> so forms appear without extra wiring.

App.vue
<script setup lang="ts">
import { IntunixProvider } from '@intunix/vue'

const config = { register: ['nps_q4'] }
</script>

<template>
  <IntunixProvider api-key="pk_live_xxx" :config="config">
    <RouterView />
  </IntunixProvider>
</template>

Plugin form

Provides the client app-wide. You render the host yourself, once, wherever you want forms to mount.

main.ts
import { createApp } from 'vue'
import { createIntunix } from '@intunix/vue'
import App from './App.vue'

const intunix = createIntunix({
  apiKey: 'pk_live_xxx',
  config: { register: ['nps_q4'] },
})

createApp(App).use(intunix).mount('#app')
App.vue
<script setup lang="ts">
import { ExperienceHost, useIntunixContext } from '@intunix/vue'

const { renderer } = useIntunixContext()
</script>

<template>
  <RouterView />
  <ExperienceHost :renderer="renderer" />
</template>

Plain HTML — @intunix/web

Forms render inside a Shadow DOM, so your site's CSS cannot leak into the form and the form's CSS cannot leak into your site. Nothing to build, nothing to bundle.

index.html
<script>
(function(w,d,s,u){
  w.intunix = w.intunix || function(){ (w.intunix.q = w.intunix.q || []).push(arguments) };
  w.intunix.l = +new Date;
  var js = d.createElement(s), f = d.getElementsByTagName(s)[0];
  js.async = 1; js.src = u;
  f.parentNode.insertBefore(js, f);
})(window, document, 'script', 'https://cdn.intunix.com/sdk/cx.js');

intunix('init', 'pk_live_xxx');
</script>

The first three lines install a queueing stub, so calls made before the script downloads are replayed once it arrives. Load it in <head> and call intunix('init', …) immediately.

WordPress

Install the Intunix plugin from the WordPress plugin directory, then:

  1. Go to Settings → Intunix.
  2. Paste your public client key.
  3. Either tick Register all forms, or list specific form slugs.

The plugin injects the loader snippet on every page and passes your registration choice through. No theme edits and no code.

Shopify

Install the Intunix app from the Shopify App Store and connect it to your workspace. The app injects the SDK through a theme app extension, so it survives theme updates. Form registration is configured in the app, exactly as in WordPress.

Custom renderer — @intunix/core

If you are targeting a framework without an official binding, install the core and supply a Renderer. It has two methods:

code
import { IntunixClient, type Renderer } from '@intunix/core'

const myRenderer: Renderer = {
  // Show the experience, then resolve when the user is finished with it.
  async render(exp, ctx) {
    ctx.onImpression()                       // form became visible
    // ...your UI. Call ctx.onResponse(answer) / ctx.onDismiss() as it happens.
    return { experienceId: exp.id, outcome: 'responded', response: answer }
  },
  // Force-close a form that is currently on screen.
  dismiss(experienceId) {
    // ...
  },
}

const client = new IntunixClient({ renderer: myRenderer })
await client.init('pk_live_xxx', { register: ['nps_q4'] })

The outcome you return drives cadence, so report it honestly: 'responded' when they answered, 'dismissed' when they actively closed it, and 'timeout' when they ignored it. See Cadence.

Requirements

  • Any evergreen browser. The SDK uses standard DOM APIs and needs no polyfills.
  • localStorage and sessionStorage. If both are unavailable (hard-blocked cookies, some privacy modes) the SDK degrades to in-memory state — forms still show, but cadence resets on every page load.
  • No jQuery, no CSS file to import, no fonts fetched.