Skip to content
Documentation

Put the configurator on your own site

One line of HTML. No framework, no build step, no cookie. The widget draws itself inside a shadow root, so your stylesheet and ours cannot reach each other.

Installed in a minute

Paste this into the page, exactly where the widget should appear. That is the whole installation. The slug lives in the portal under Settings → Lead forms, next to the button that generates this line for you. Building in React, Next.js or Vue? Use placeholders — see further down.

<script src="https://app.zinevu.com/w.js" async
        data-zv="YOUR_FORM_SLUG"
        data-widget="size"
        data-url="https://offerte.yourcompany.nl/q/YOUR_FORM_SLUG"></script>

data-url is where the visitor goes. Keep it: it is also the safety net for the day the widget cannot read its settings — it shows a working button instead of a hole in your page.

  • async — the widget never delays your page and never blocks rendering.
  • Two globals — Zinevu and the internal __zinevuWidget — and nothing in your <head>: no stylesheet, no extra tag.
  • Every failure is swallowed. A broken widget must not take a website down with it.
  • Whatever the dealer changes in the portal — a renamed question, an option switched off — is live on your site. You never re-paste the snippet.

Nine widgets

Pick one with data-widget. They share the same card and the same attributes; they differ in what they ask and what they hand back.

Size box

size

Asks for width and depth and hands the visitor to the form with those measurements already filled in.

The default choice. Works in a sidebar, between two paragraphs, or as a hero block on a product page.

<script src="https://app.zinevu.com/w.js" async
        data-zv="SLUG" data-widget="size"
        data-url="https://offerte.yourcompany.nl/q/SLUG"></script>

Indicative price

price

The same size question, but it also shows what the standard build costs at that size.

On a page where visitors drop out because they have no idea of the order of magnitude.

Only works when the dealer has switched public prices on for this form. With that switch off the widget falls back to the button — it never shows an invented number.

<script src="https://app.zinevu.com/w.js" async
        data-zv="SLUG" data-widget="price"
        data-url="https://offerte.yourcompany.nl/q/SLUG"></script>

Size check

match

Asks for a measurement and says whether it can be built from stock panes or becomes made-to-measure.

Glass sliding walls. A visitor whose measurement is a stock size can go straight to your shop.

Only available on forms of the glass sliding wall type.

<script src="https://app.zinevu.com/w.js" async
        data-zv="SLUG" data-widget="match"
        data-shop="https://yourcompany.nl/p/sliding-wall?w={width}&h={height}"
        data-url="https://offerte.yourcompany.nl/q/SLUG"></script>

One question

question

Shows a single question from the form — the roof material, say — and carries the answer into the funnel.

On a page about exactly that subject. The visitor starts with a choice they were making anyway.

<script src="https://app.zinevu.com/w.js" async
        data-zv="SLUG" data-widget="question" data-step="roof"
        data-url="https://offerte.yourcompany.nl/q/SLUG"></script>

Button

cta

A button or bar that opens the funnel. Asks nothing.

Site-wide, in a footer or under every product page. This is the widget that may sit on every page.

With data-url this widget makes no network request at all — it paints immediately. With data-mode="floating" it becomes a button fixed bottom-right, which can sit in a footer on every page without moving anything.

<script src="https://app.zinevu.com/w.js" async
        data-zv="SLUG" data-widget="cta" data-open="drawer"
        data-url="https://offerte.yourcompany.nl/q/SLUG"></script>

Full form

form

The whole funnel in a frame on your own page. The visitor never leaves your site.

A landing page with a single purpose, or your own funnel page for data-open="page". Give it room — this is not a sidebar widget.

By default the frame grows with the funnel. data-height="fill" makes it a screen tall; a number or CSS length gives it a fixed height. With data-target="3d" the frame is a screen tall by default, and the funnel inside can ask for a screen-tall frame itself as long as you did not set a fixed height. If your site has a fixed header, pass its height in pixels as data-offset.

<script src="https://app.zinevu.com/w.js" async
        data-zv="SLUG" data-widget="form"
        data-url="https://offerte.yourcompany.nl/q/SLUG"></script>

Every question on one page

With data-target="onepage" the frame holds the same form with every question on a single page. Each question is folded down to the answer it holds, with the alternatives beside it, so the frame stays short and the visitor sees the whole configuration at a glance. Give the frame at least 1000px of width and the drawing sits beside the questions instead of above them.

<script src="https://app.zinevu.com/w.js" async
        data-zv="SLUG" data-widget="form" data-target="onepage"
        data-url="https://offerte.yourcompany.nl/q/SLUG/direct/veranda"></script>

Appointment

book

Sends the visitor to the showroom calendar, where they pick a slot themselves.

On the contact or showroom page.

The only widget without data-zv — the booking page is not a lead form and has its own address (portal → Settings → Showroom). data-url is required here.

<script src="https://app.zinevu.com/w.js" async
        data-widget="book"
        data-url="https://offerte.yourcompany.nl/afspraak/SHOWROOM_SLUG"></script>

Book an appointment

appointment

The booking calendar for a free measurement, a consultation at home, a callback or a showroom visit. The visitor picks a day and time; for a home visit they give their postcode first, so only days you are nearby appear. Every booking arrives in the portal as a lead.

On the how-it-works, contact or landing page — or with data-mode="floating" in the footer, so the button is on every page.

No data-zv. The link comes from portal → Settings → Appointments (switch on the public booking link per appointment type); portal → Marketing → Widgets builds the code. After a booking a dataLayer event zinevu_appointment_booked follows, carrying only the appointment type and its status (see Events).

<script src="https://app.zinevu.com/w.js" async
        data-widget="appointment" data-mode="popup"
        data-url="https://offerte.yourcompany.nl/afspraak/APPOINTMENT_SLUG"></script>

Live chat

chat

A chat button in the corner of the page. The visitor asks their question, you answer from the portal or the app — and see which page they are on while they type.

Site-wide. This snippet belongs in your layout or your header once, like an analytics tag; it draws nothing where the code itself sits.

Switch the chat on first under portal → Settings → Live chat and tick “Your own website” there, or nothing appears. The greeting, the office hours and the languages live in those same settings rather than in the code: one change there applies to your whole site at once. Until the visitor presses the button, the widget stores nothing.

<script src="https://app.zinevu.com/w.js" async
        data-zv="SLUG" data-widget="chat"></script>

Every attribute

Everything is read from the script tag itself, or from the placeholder element. Unknown values are ignored rather than guessed — a typo in data-theme gives you the light theme, an unknown data-open plain navigate, not a broken widget.

AttributeApplies toValueLeft outWhat it does
data-zinevuplaceholder elementno value neededthe element stays a plain elementTurns an element into a widget, once w.js is on the page. Placeholders that appear later are mounted automatically.
data-zvRequired. Without it the widget draws nothing and says why in the console.all but book and appointment16–40 letters and digits · autothe widget draws nothing — except a form placeholder on your funnel page, which takes the slug from zv_form in the addressThe lead form's slug. Found in the portal on the form itself. auto (or leaving it out) only on a form placeholder on your own funnel page.
data-widgetallsize · price · match · question · cta · form · book · appointment · chatsizeWhich widget is drawn.
data-urlall · required for book and appointmentabsolute URLthe widget asks for the address itselfWhere the visitor goes. Also the safety net when settings cannot be read, and for cta and form it saves a network request entirely.
data-mountsingle script tag onlyCSS selectorthe script tag's own positionPuts the widget inside an existing element. If the selector matches nothing the widget does not draw and says so in the console.
data-localeallauto · nl · en · de · fr · trsingle script tag: nl · placeholder: autoThe language of the widget, and of the funnel it hands over to. With auto the widget reads what language your page is in — a language switcher, WPML, Polylang, GTranslate, Google Translate — and changes with it the moment a visitor switches. Which languages actually exist is the dealer's setting (portal → settings → lead forms): ask for one that is switched off and the widget comes back in one that is on.
data-targetall3d · onepage · quickform · a placeholder on your funnel page follows zv_targetHands over to the 3D configurator, with onepage to the same form with every question on one page, or with quick to the quick quote (size, budget, contact details — answered with packages within the budget), instead of the flat step form. If the form does not run the quick quote, quick goes to the ordinary form.
data-refallyour own text, max 120 charactersnoneYour own reference for this visit. It travels with the request and comes back on the zinevu:lead event, so your page knows which visit became a request — something we do not tell you, because we say nothing about the visitor. We never read it, we only carry it. If one is already on the address (a button earlier in the route set it), that first reference stays.
data-stepall · required for questionstep keythe funnel opens at step oneOpens the funnel at a given question. On the question widget it decides which question is shown.
data-themealldarklightThe dark variant, for a dark section on your site.
data-accentall#rrggbbthe form's own accent colourOverrides the accent colour. Six hex digits only; the text colour on the button is computed from it, so a pale brand colour gets black letters instead of unreadable white.
data-layoutallherocardPuts a second column beside the card for a heading, an explanation and a reassurance. Only appears if you also pass data-title, data-intro or data-note.
data-titleallfree textthe built-in textThe heading above the widget.
data-labelallfree textthe built-in textThe button's caption.
data-introhero layoutfree textno second columnThe sentence that makes somebody use the widget.
data-notehero layoutfree text—The small line underneath: lead time, no obligation, warranty.
data-openall but form · on book and appointment only drawer and modalnavigate · page · drawer · modalnavigateWhere the funnel opens: the funnel's address in the same tab, your own funnel page (with data-page), a panel from the side, or a centred panel. A panel is deliberately never the default. See Where the funnel opens.
data-pagedata-open="page"path or URL of your funnel pagedata-open falls back to navigate, with a console messageYour own page with a form placeholder on it. The visitor goes there with their answers, zv_form and zv_target in the address.
data-modalsame as data-open1offThe old spelling of data-open="modal". Still works; when data-open is also present, data-open wins.
data-modeappointment · ctainline · popup · floating (cta: floating)appointment: inline · cta: a card with a buttonHow the widget appears: in the page, as a button that opens a panel, or as a floating button bottom-right that can sit in a footer on every page. A floating booking button opens a panel; a floating cta follows data-open.
data-positionchatright · leftbottom rightWhich corner the chat button sits in. data-side and data-bottom (pixels, 20 by default) move it clear of anything already there — a cookie bar, or a back-to-top button.
data-heightform · book and appointment in the pageauto · fill · number (px) · CSS lengthauto — a screen tall with data-target="3d"auto grows with the funnel. fill makes the frame a screen tall, less data-offset. A fixed height stays put, even when the funnel asks for a screen-tall frame itself.
data-offsetform · book and appointment in the pagenumber (px)0The height of your site's fixed header. A screen-tall frame is that much shorter, and when the funnel scrolls to its frame it stops that far below the top.
data-name · data-email · data-postcodeappointmentfree textemptyPrefills the calendar with what your own page already knows, for example from a logged-in customer. Nothing is read off the visitor.
data-aronly with data-target="3d"1offOpens the 3D configurator straight into AR, so the visitor sees the product at true size in their own garden. Ignored on the flat form.
data-shopmatchURL with {width} {height} {panels} {pane_width} {pane_height}no shop linkWhere a visitor with a stock measurement may go. Only appears when the measurement really can be built from stock.
data-shop-labelmatchfree textthe built-in textThat link's caption.
data-cartmatchURL on your own domainno cart buttonYour own endpoint that receives the measurement and answers with where to send the visitor. Everything that decides whether the item may exist and what it costs happens there — server-side, on your domain.
data-productmatchproduct idno cart buttonWhich article your endpoint adds. Without both data-cart and data-product the button does not appear.
data-originallorigin, e.g. https://app.zinevu.comthe address w.js was loaded from (else https://app.zinevu.com)Advanced: reads settings, indicative prices and size checks from a different API origin. Normally you leave it out.

Where the widget lands

By default the widget appears exactly where the script tag sits. That is what makes the install a single paste — you put the line where you want the block.

  • Paste the line into a "header scripts" field — how half of WordPress sites load third-party tags — and it lands in <head>. The widget refuses to draw there and says in the console what to do instead. It deliberately does not move itself: a header snippet is on every page, so a widget that relocated would appear above your navigation site-wide.
  • If you do want to load it from the header, pass data-mount with a selector for the element it should go into — or load w.js in the header and put a <div data-zinevu> placeholder in the page.
  • The widget lives in a shadow root. Your CSS cannot reach it and ours cannot reach your page — a reset or an aggressive * { } in your theme cannot break it. What you may change goes through the --zinevu-* properties and ::part() (see Theming).
<!-- from a header field, with a target on the page -->
<div id="quote-block"></div>

<script src="https://app.zinevu.com/w.js" async
        data-zv="SLUG" data-widget="size"
        data-mount="#quote-block"
        data-url="https://offerte.yourcompany.nl/q/SLUG"></script>

React, Next.js, Vue and other single-page sites

On a site that renders its own markup and navigates without reloading, the script tag cannot be the widget itself. React de-duplicates identical async scripts, so a second tag on the same page never runs, and a client-side navigation never re-runs the first. Load the loader once there, and put a placeholder wherever a widget should appear.

  • A bare script tag — no data-zv, no data-widget — draws nothing where it sits. It is only the loader: every element carrying data-zinevu becomes a widget.
  • Placeholders that appear later — after a client-side navigation, in a tab that renders on click, in a block your CMS injects — are mounted automatically. If a placeholder disappears while its panel is open, that panel is closed, so the next page still scrolls.
  • Every attribute in the table works on a placeholder too, except data-mount: the placeholder is the place.
  • Without data-locale a placeholder follows your page's language, exactly as data-locale="auto" would. A single script tag still defaults to nl unless you write auto.
  • Both ways can be mixed on one page. They share one API, one settings cache and one bridge to the frames.
<!-- once, for example in your root layout -->
<script src="https://app.zinevu.com/w.js" async></script>

<!-- wherever a widget should appear -->
<div data-zinevu data-zv="SLUG" data-widget="price"></div>

<div data-zinevu data-zv="SLUG" data-widget="question"
     data-step="roof" data-open="drawer"></div>

window.Zinevu

The same placeholders, by hand. Useful when you decide yourself when a component comes into view, or want to open the funnel from your own button. Options are the data-* attributes without the prefix: { zv, widget, target: "3d", open: "drawer" }. A camelCase name is written with dashes (shopLabel → data-shop-label) and true becomes "1".

  • Zinevu.mount(el, options) — draws a widget into el. The options are written onto the element as data-* attributes, so a widget always reads the same way however it got there. A widget already in el is replaced.
  • Zinevu.unmount(el) — takes the widget out again, and closes a panel it had open.
  • Zinevu.scan(root) — mounts every [data-zinevu] under root that is not a widget yet; without root, the whole document. Rarely needed: the loader watches for new placeholders itself.
  • Zinevu.open({ zv, target, open, … }) — opens the funnel straight away in a panel over the page, without drawing anything. open is drawer (the default) or modal. With url included nothing needs to be fetched.
  • Zinevu.consent({ statistics, marketing }) — tells the widget what your cookie banner decided. See Privacy and cookies.
  • Zinevu.tags({ gtm: "GTM-XXXX", ga4: "G-XXXX", pixel: "123456" }) — names the measurement tags your own page already runs. When Zinevu is set up with the same GA4 property or the same Meta pixel, the frame stops counting the visitor a second time: the page view is left to your page, the enquiry is still reported. Rarely needed — the loader finds what is already on the page by itself; set it when your own tags load only after your cookie banner does.
  • Zinevu.setLocale("en") — states what language your page is in now. Every widget with data-locale="auto" is redrawn at once, an embedded form included. You need it when your switcher does not reload the page (Weglot, WPML over AJAX, your own React router); a switcher that reloads needs nothing. Called with nothing, the widget goes back to reading the page itself. Zinevu.locale() says what it reads right now.
  • The loader is async, so window.Zinevu only exists once it has loaded. If you do not want to wait for that, use a placeholder: it is mounted as soon as the loader arrives. If your page already has something called Zinevu, the loader leaves it alone.
// React: mount, and clean up again
useEffect(() => {
  const el = ref.current;
  window.Zinevu?.mount(el, { zv: "SLUG", widget: "size", target: "3d" });
  return () => window.Zinevu?.unmount(el);
}, []);

// your own button opens the 3D configurator in a side panel
button.addEventListener("click", () => {
  Zinevu.open({ zv: "SLUG", target: "3d", open: "drawer" });
});

Where the funnel opens

What happens once the visitor has answered a widget is chosen with data-open. It applies to every widget that hands off — size, price, match, question and cta. On book and appointment, drawer and modal turn the widget into a button that opens a panel.

  • navigate (default) — the funnel's own address, in the same tab. What every link on your site already does.
  • page — your OWN funnel page (data-page="/quote") holding a form placeholder. The visitor stays on your domain, the address bar is yours, back and reload work. Without data-page the widget falls back to navigate and says why in the console.
  • drawer — a panel sliding in from the side over your page; full screen on a phone.
  • modal — the same panel, centred. data-modal="1" is the old spelling and still works. A panel closes with its button, Escape or a click on the backdrop, and hands scrolling and focus back to your page.
  • The hand-off to your funnel page carries the answers as zv_* parameters, plus zv_form (which form) and zv_target (3d when the visitor chose the 3D configurator). Click ids and UTMs travel along as with navigate.
  • A form placeholder without data-zv, or with data-zv="auto", takes the slug from zv_form — so one funnel page serves all your forms. Only a placeholder reads these: a single script tag on your funnel page ignores zv_form, zv_target and the answers.
<!-- product page: the size box hands off to your own funnel page -->
<div data-zinevu data-zv="SLUG" data-widget="size"
     data-open="page" data-page="/quote"></div>

<!-- /quote: one placeholder for every form,
     72px fixed header above the page -->
<div data-zinevu data-widget="form" data-zv="auto"
     data-offset="72"></div>

If somebody opens your funnel page directly, without zv_form in the address, an auto placeholder cannot know which form is meant and draws nothing. If your site has only one form, simply give it the slug.

Theming

The shadow root keeps your stylesheet out, with two declared exceptions. CSS custom properties do cross a shadow boundary: set them on the placeholder, on a section or on :root. For anything they cannot say there are ::part() names. Set nothing and the widget looks the way it always has.

  • --zinevu-accent — button and focus colour. Your value outranks the colour stored with the form; data-accent on the snippet outranks both.
  • --zinevu-on-accent — label colour on the accent (computed otherwise).
  • --zinevu-fg · --zinevu-muted — text and secondary text.
  • --zinevu-surface · --zinevu-field · --zinevu-panel — the card, the inside of an input or tile, and the hero section behind the card.
  • --zinevu-line · --zinevu-line-strong — hairlines.
  • --zinevu-shadow — the card's shadow (none for flat).
  • --zinevu-radius · --zinevu-control-radius — card and panel corners; input, button and tile corners.
  • --zinevu-font · --zinevu-heading-font · --zinevu-heading-weight — the body font (inherited from your page otherwise), the font of headings and the amount, and the weight of headings.
  • --zinevu-card-max-width — the card's width cap.
  • ::part() — root, card, title, field, field-label, input, button, button-secondary, button-floating, button-label, button-arrow, result, amount, basis, note, tile, tile-image, tile-label, hero, hero-title, hero-intro, hero-note, frame, overlay, panel, panel-bar, panel-title, close.
.quote-section {
  --zinevu-accent: #0f766e;
  --zinevu-radius: 8px;
  --zinevu-control-radius: 6px;
  --zinevu-shadow: none;
  --zinevu-heading-font: Georgia, serif;
}

/* ::part() hangs off the host element */
[data-zinevu]::part(button),
[data-zinevu-widget]::part(button) {
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

The host element is [data-zinevu] for a placeholder, [data-zinevu-widget] for a single script tag and [data-zinevu-open] for Zinevu.open(). Set the properties in your stylesheet rather than later from JavaScript: whether your accent colour wins is decided when the widget draws.

Events for your own analytics

The widget measures nothing and sends nothing itself. What happens, it announces to your page — twice, for the two kinds of site: a dataLayer push for a Tag Manager container, and a DOM event on document with a detail for a site that wires its own analytics. Your tags, and the consent rules they run under, decide what happens next.

  • zinevu_open · zinevu:open — a widget handed the visitor to the funnel or opened a panel. dataLayer: zv_widget, zv_form_type, zv_target (form or 3d), zv_open. detail: widget, form_type, target, open.
  • zinevu_price · zinevu:price — the price widget answered. dataLayer: zv_form_type, zv_priceable. detail: form_type, priceable. Only whether a price was shown, never the amount: that is the dealer's price list, and it does not belong in your analytics.
  • zinevu_lead · zinevu:lead — a lead was sent from an embedded funnel. dataLayer: zv_form_type, zv_surface. detail: form_type, surface.
  • zinevu_appointment_booked · zinevu:appointment_booked — a booking was made on an embedded booking page. dataLayer: zv_appointment_kind, zv_appointment_status. detail: kind, status.
  • If your Tag Manager container is also configured in Zinevu, it runs twice on this page: on your site and inside the frame. So the frame sets zv_embedded (true) and zv_container_on_host in its own dataLayer, before the container loads. Make a Data Layer Variable of it and attach that as a blocking trigger to the tags that count a page view — your Google tag, for instance. Leave the tags that count an enquiry alone: that only ever happens inside the frame.
  • lead and appointment_booked only come from a funnel that sits on your page: the form widget, a panel, or your funnel page with data-open="page". With navigate the visitor leaves your site before sending anything.
// Tag Manager: trigger on Custom Event "zinevu_lead"
// dataLayer.push({ event: "zinevu_lead", zv_form_type: "…", zv_surface: "…" })

// Or yourself, with gtag:
document.addEventListener("zinevu:lead", (e) => {
  gtag("event", "generate_lead", { form_type: e.detail.form_type });
});

Which product and which surface — never who, never an amount.

Per platform

It is one script tag, so anywhere you can put HTML it works. These are the places that live in each system.

WordPress

  1. 1With the Zinevu plugin: download it from https://app.zinevu.com/zinevu-widgets.zip — it is not in the WordPress plugin directory — upload it under Plugins → Add New → Upload Plugin, paste your script line once under Settings → Zinevu Widgets, and then place the "Zinevu widget" block in the editor. You pick the type and the options in the block itself.
  2. 2Without the plugin: add a "Custom HTML" block in the block editor and paste the script line into it.
  3. 3In a classic theme: paste the line into the page template where the widget belongs.

Watch out for optimisation plugins. Exclude the widget from "defer JS" and "combine scripts": the tag needs its own position in the page and loses it when it is moved or merged.

Shopify

  1. 1Go to Online Store → Themes → Edit code.
  2. 2Create a new section, paste the script line into it and save.
  3. 3Add the section to the template where the widget should appear.

If you want the size check to write into your own cart, point data-cart at an endpoint on your own domain. Price and stock belong there, not in the browser.

Wix

  1. 1Add an "Embed a widget" element (Custom Embed → Embed a Widget) where the widget belongs.
  2. 2Paste the script line into the HTML field.
  3. 3Give the element enough height — Wix clips at the height you set.

Webflow

  1. 1Drag an Embed element to the right place.
  2. 2Paste the script line into it.
  3. 3Publish; in the designer the element stays empty, which is expected.

Plain HTML or another CMS

  1. 1Paste the script line into the body, where the widget should appear.
  2. 2If your CMS only allows scripts in the header, use data-mount with a selector.

React, Next.js or Vue

  1. 1Load w.js once as a bare script tag, without data-zv, for example in your root layout or index.html.
  2. 2Put <div data-zinevu data-zv="SLUG" data-widget="…"> in the component where the widget belongs.
  3. 3Client-side navigation needs nothing extra: new placeholders are mounted automatically.

If you want to decide yourself when a widget comes and goes, use Zinevu.mount and Zinevu.unmount — see window.Zinevu.

Privacy and cookies

The widget knows nothing about the visitor. No cookie is set, no localStorage is touched, no identifier is minted and no beacon is sent. Its settings come from a public read that returns exactly the same answer for everyone; the price widget and the size check send only the measurement that was typed in.

  • That is why the widget may run before your cookie banner has been answered: there is nothing to consent to.
  • You do not need to put it behind a consent category. If you do it still works — but you lose the visitors who dismiss the banner.
  • It does read one thing: the three Complianz cookies (cmplz_banner-status, cmplz_statistics, cmplz_marketing), and nothing else. They are passed on unchanged to an embedded funnel, so it never asks for consent a second time. A visitor who answers your banner while the funnel is open is counted within two seconds.
  • Using a different cookie banner? State what the visitor chose yourself: Zinevu.consent({ statistics: true, marketing: false }). It outranks the cookies and also reaches frames that are already open. Call it again when the visitor changes their choice.
  • The dealer's own cookie and tracking settings apply from the moment a visitor opens the funnel. That is a different domain — even when the funnel sits in a frame on your page.

This is a deliberate property, not an accident. The moment the widget stored or sent anything about the visitor, every site running it would need a consent gate in front of it.

Ad attribution

A visitor who lands on your site from an ad and then clicks the widget would arrive in the funnel with no click id and your own site as the referrer. The lead from that paid click reads as direct traffic, and the sale it becomes weeks later can never be reported back to the campaign.

  • Click ids from the URL (Google, Meta) are carried into the funnel under their own names.
  • The real landing page and the original referrer travel separately, so your domain does not overwrite them.
  • Which widget the visitor used travels along too. The request in the portal then reads, for example: Price widget on /veranda.
  • There is nothing to configure. It happens as long as the visitor clicks on the same page they arrived on.

When it does not work

I see nothing on the page.
Open the console. The widget always says why it did not draw. The two common causes: the snippet is in <head> (paste it into the page, use data-mount or a placeholder), or data-zv is not a valid form slug. A placeholder only draws once w.js has loaded somewhere on the page.
My site switches language but the form stays Dutch.
The form is a frame on our address, and no translation your site performs on itself can reach inside it — Google Translate included. Put data-locale="auto" on the snippet (or paste a fresh one from the portal): the widget then reads what language your page is in and loads the funnel in it. If your switcher changes language without reloading the page, call Zinevu.setLocale("en") when it does. If the funnel still comes back in one language, that language is switched off for this dealer: portal → settings → lead forms → languages.
In React or Next.js only one widget appears, or it disappears after a navigation.
Then there is one script tag per widget. React de-duplicates identical async scripts, and a client-side navigation never re-runs a script. Load w.js once as a bare tag and use <div data-zinevu> placeholders.
With data-open="page" the visitor still goes to the funnel address, or my funnel page stays empty.
The first means data-page is missing; the console says so. For the second, check that your funnel page holds a placeholder (not a single script tag) with data-widget="form", and that the address carries zv_form — or give the placeholder a fixed data-zv.
I see no zinevu_lead in my dataLayer.
That event only comes from a funnel that sits on your page: the form widget, a panel (drawer or modal) or your funnel page with data-open="page". With navigate the visitor leaves your site before sending the request.
The console says data-mount matched nothing.
The element does not exist yet when the script runs, or the selector is wrong. Put the target element above the script tag in the HTML.
The price widget only shows a button.
That is the graceful fallback. The dealer has not switched public prices on for this form, or the given measurement yields no price. The widget would rather show no number than a wrong one.
The widget looks different from the rest of my site.
That is by design — it sits in a shadow root, out of reach of your stylesheet. Tune it with data-accent, data-theme and data-layout, or with the --zinevu-* properties and ::part() from Theming.
My caching plugin broke the widget.
Exclude w.js from "combine", "defer" and "lazy load JS". The tag has to stay in its own position in the page.
I run a strict Content-Security-Policy.
Allow the script origin you load the loader from. The widget fetches nothing further itself: no stylesheet, no font, no framework.
The shop button does not appear on the size check.
It only appears when the given measurement really can be built from stock panes. If it is made-to-measure there is no product page to send anyone to, and the widget offers the quote route instead. The cart button additionally needs both data-cart and data-product.
Documentation

The snippet is waiting in the portal

Under Settings → Lead forms there is a generator with a live preview: pick the type, the colour and the wording, and copy the line. It is the same line as above, with the slug already in it.