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
| Package | What it gives you | Peer requirements |
|---|---|---|
@intunix/react | Provider component, hooks, React-rendered forms | react >= 18, react-dom >= 18 |
@intunix/vue | Provider component or plugin, composables, Vue-rendered forms | vue >= 3 |
@intunix/web | Drop-in cx.js, Shadow DOM rendering, window.intunix() queue | None — plain script tag |
@intunix/core | IntunixClient and types only. You supply the Renderer. | None |
React
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:
'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>
}import { IntunixClientProvider } from '@/components/IntunixClient'
export default async function RootLayout({ children }) {
return (
<html lang="en">
<body>
<IntunixClientProvider>{children}</IntunixClientProvider>
</body>
</html>
)
}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:
// 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.
ARG NEXT_PUBLIC_INTUNIX_CLIENT_KEY
ENV NEXT_PUBLIC_INTUNIX_CLIENT_KEY=$NEXT_PUBLIC_INTUNIX_CLIENT_KEY
RUN npm run buildVue 3
npm install @intunix/vueComponent form
Mirrors the React provider: creates the client, provides it to descendants, and mounts an <ExperienceHost> so forms appear without extra wiring.
<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.
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')<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.
<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:
- Go to Settings → Intunix.
- Paste your public client key.
- 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:
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.
localStorageandsessionStorage. 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.