Перейти к содержимому

Build your own UI

Это содержимое пока не доступно на вашем языке.

Headless mode hides Togenar’s built-in option panel while the configuration engine and SDK keep running. Your design system renders the swatches; the SDK drives the model.

<togenar-embed id="tg" project="" configurator picker="off" controls="off"></togenar-embed>

Two switches, two surfaces:

Switch Turns off You draw instead
picker="off" The option panel — swatches, part sections, summary. (mode="headless" is equivalent.) Your swatches, driven with getOptions() + select()
controls="off" The whole control surface — AR button, snapshot button, ruler icon, reset-view button. Your buttons, driven with the commands in step 6

Take either on its own: a page that wants Togenar’s panel but its own AR button sets only controls="off". controls is one switch for the entire surface — named after <video controls> — so a control added to the viewer later never needs a new attribute here, and you never end up with two AR buttons stacked on the same corner.

getOptions() returns every part with all of its variants, ready to render:

const parts = await tg.getOptions();
/* [{
partId, partKey: 'body', label: 'Body', defaultVariantId,
inPicker, // does the published panel offer this part to visitors?
link, // { role: 'driver' | 'driven', … } when it belongs to a link group
variants: [{
variantId, handle: 'walnut', label: 'Walnut', sku: 'OSLO-BODY-WALNUT',
swatch, // color hex, when the variant defines one
swatchImage, // swatch texture URL, when it defines one
modelName, isDefault, available
}, …]
}, …] */

Render these in your own components — swatch / swatchImage give you native-looking pickers without re-deriving anything.

Render the options, not the parts. A product with coordinated finishes has one part the shopper chooses and many that follow it — offering the followers invites contradicting picks. inPicker already carries that decision from the panel, including the author’s per-part exceptions, so filter on it rather than hardcoding part keys:

const options = parts.filter((p) => p.inPicker);

Selection keys are the same handles getOptions() returned (they’re also what the share URL uses after ?part=):

const { ok, selection } = await tg.select('body', 'walnut');

select() resolves after the model swap settles — so you can update price, cart state or imagery exactly when the shopper sees the change. It rejects on unknown handles.

reset() returns the configuration to the published default.

// Live stock — false marks a variant out of stock:
await tg.setAvailability({ body: { walnut: false, oak: true } });
// Price display — keyed by the same SKUs the events report:
await tg.setPrices({
currency: 'EUR', locale: 'de-DE',
items: { 'OSLO-BODY-WALNUT': 129.9 },
total: 259.8,
});

The viewer never computes prices — your page stays the single source of pricing truth. See Prices in the configurator.

Even in headless mode the engine emits togenar:configurator:selection_change on load and on every select() — treat it as your single source of selection state instead of mirroring state in your own code.

With controls="off" the viewer renders no buttons of its own, and each one has a command:

Your button Command
View in your room enterAR() — call it from your own click handler; WebXR needs an in-page gesture
Snapshot getSnapshot() — resolves with a PNG data URL
Measurements setDimensions(on?) — omit the argument to flip it
Reset view resetCamera()

A button with nothing behind it is worse than no button: measurements can be switched off for the project, AR is not launchable for every configuration, and “reset view” means nothing while the camera is still in its opening framing. So the viewer reports what it would have offered, and your buttons follow it:

tg.addEventListener('togenar:controls', (e) => {
const { ar, photo, dimensions, resetView } = e.detail;
arBtn.hidden = !ar;
snapshotBtn.hidden = !photo;
measureBtn.hidden = !dimensions.enabled;
measureBtn.setAttribute('aria-pressed', String(dimensions.on));
resetBtn.hidden = !resetView;
});

getControls() returns the same shape synchronously, for a first paint before any event has fired. The event fires on every change — a shopper dragging the model, an option swap that changes AR availability, the overlay being toggled from your own button.