React, Vue and plain HTML
import { useEffect } from 'react';import { useDuorConfigurator } from '@duordigital/embed/react';
export function KitchenPage() { const { ref, configurator } = useDuorConfigurator('kitchen-terra', { origin: 'https://app.duor.digital', apiKey: import.meta.env.VITE_DUOR_KEY, locale: 'en', });
useEffect(() => configurator?.on('lead', (lead) => crm.createLead(lead)), [configurator]);
return <div ref={ref} style={{ height: 640 }} />;}The hook cleans up after itself on unmount, and survives a hot reload without leaving a second iframe behind. Mounting two on one page is fine; each keeps its own scene.
<script setup>import { useDuorConfigurator } from '@duordigital/embed/vue';
const { container } = useDuorConfigurator('kitchen-terra', { origin: 'https://app.duor.digital', apiKey: import.meta.env.VITE_DUOR_KEY,});</script>
<template><div ref="container" style="height: 640px" /></template>Mounting it by hand is a trap worth naming: createConfigurator returns a
promise, so a cfg assigned from it has no destroy to call on unmount —
the widget outlives the component and the next mount adds a second one.
Anything else
Section titled “Anything else”<div id="cfg" style="height:640px"></div><script src="https://app.duor.digital/sdk/v1.js"></script><script> DuorEmbed.createConfigurator('kitchen-terra', document.getElementById('cfg'), { origin: 'https://app.duor.digital', apiKey: 'pk_live_…', });</script>Making it look like yours
Section titled “Making it look like yours”Two options change the widget’s appearance, and deliberately only two:
{ accent: '#1f6feb', font: 'Inter' }accent repaints every call to action; font swaps the interface face. The
dimensions stay in a monospace face whatever you pass, because 1 and 7 in a
proportional face at 11px is a guess, and these are millimetres somebody will
cut wood by.
There is no stylesheet override and there will not be one: a widget whose internals a host can restyle is a widget that breaks on the host’s next deploy, and the support ticket comes to us.