Skip to main content

Full Customizer (@chamevo/customizer)

The fastest way to add a product customizer to any site. Install one package, drop in a web component, and you have a working customizer with a complete UI.

When to use: You want a full-featured customizer with minimal code. The built-in UI handles text editing, image uploads, layer management, color picking, and more.

Installation

npm install @chamevo/customizer

Or via CDN:

<script src="https://cdn.jsdelivr.net/npm/@chamevo/customizer/dist/chamevo.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@chamevo/customizer/dist/chamevo.min.css" />

Two Ways to Initialize

Option A: createCustomizer() Factory

import { createCustomizer } from '@chamevo/customizer';

const customizer = createCustomizer('#container', {
stageWidth: 900,
stageHeight: 600,
});

customizer.addEventListener('cvReady', async (e) => {
await customizer.loadProduct(myProduct);
});

createCustomizer(target, options) registers all web components, creates a <cv-customizer> element, and appends it to the target container.

In addition to all ChamevoOptions, the factory accepts one extra parameter:

OptionTypeDescription
resourcesUrlstringAbsolute URL to the directory containing the Stencil lazy-chunk files. Required when the bundle is served from a different origin or path than the page (e.g. WordPress admin, CDN, subdirectory deploy).
createCustomizer('#container', {
stageWidth: 900,
resourcesUrl: 'https://cdn.example.com/assets/chamevo/js/',
});

Without resourcesUrl, Stencil resolves lazy chunks relative to document.baseURI. If your JS assets are hosted at a different path this will 404 — set resourcesUrl to the directory containing cv-customizer.entry.js to fix it.

note

resourcesUrl is only read on the first createCustomizer() call per page. If you need to change it, reload the page.

Option B: Declarative HTML

<cv-customizer
id="customizer"
options='{"stageWidth": 900, "stageHeight": 600}'
color-scheme="light"
ui-style="default"
>
</cv-customizer>

<script type="module">
import { defineCustomElements } from '@chamevo/customizer';
defineCustomElements();

const customizer = document.getElementById('customizer');
customizer.addEventListener('cvReady', async (e) => {
await customizer.loadProduct(myProduct);
});
</script>

Properties

PropertyAttributeTypeDefaultDescription
optionsoptionsstring | Partial<ChamevoOptions>'{}'Customizer configuration. Pass as JSON string in HTML or object in JS.
colorSchemecolor-scheme'light' | 'dark''light'Color theme.
uiStyleui-style'default' | 'rounded' | 'sharp''default'Shape preset (border radius, shadows).
moduleDisplaymodule-display'sidebar' | 'dialog''sidebar'How module panels are displayed.
shadowshadow'none' | 'sm' | 'md' | 'lg''none'Box shadow preset on the customizer container.
proxyFileServerproxy-file-serverstring''Proxy URL for cross-origin images and fonts.

Methods

All methods return Promises and are called on the <cv-customizer> element:

const customizer = document.querySelector('cv-customizer');
MethodReturnsDescription
loadProduct(product)Promise<void>Load a CVProduct or legacy CVView[].
selectView(index)Promise<void>Switch to a view by index.
getProduct()Promise<CVProduct | null>Get the current product with all customizations.
getOrder()Promise<object>Get product + used fonts + used colors.
getElements(viewIndex?, type?)Promise<object[]>Get serialized elements. type: 'all', 'text', 'image'.
undo()Promise<void>Undo last action.
redo()Promise<void>Redo last undone action.
reset()Promise<void>Clear all user-added elements in all views.
setProducts(catalog)Promise<void>Set the product catalog for the products module.
setGraphics(catalog)Promise<void>Set the graphics catalog for the graphics module.
registerModule(moduleId, target)Promise<() => void>Mount a built-in or custom module into an external DOM element. Returns a cleanup function.

Events

Events are dispatched as CustomEvent on the <cv-customizer> element:

customizer.addEventListener('cvReady', (e) => {
const { chamevo, canvas } = e.detail;
});
Evente.detailDescription
cvReady{ chamevo: ChamevoJS, canvas: ChamevoCanvas }Customizer fully initialized. Access core instances here.
cvProductCreate{ product: CVProduct }Product loaded.
cvProductChange{ product: CVProduct }Product data changed.
cvViewSelect{ view: CVView, index: number }Active view changed.
cvElementSelect{ element: object | null }Element selected or deselected.
cvPriceChange{ price: number, singleProductPrice: number, pricingRulesPrice: number }Price updated.

Layout & Slots

cv-customizer uses named slots for layout customization:

<cv-customizer options='{"stageWidth": 900}'>
<div slot="toolbar-top">Custom top toolbar</div>
<div slot="sidebar">Custom sidebar content</div>
<div slot="panel">Custom right panel</div>
<div slot="toolbar-bottom">Custom bottom toolbar</div>
</cv-customizer>

Responsive Behavior

The customizer automatically adapts to its container size:

Container WidthLayout
< 580px (small)Bottom tab bar + dialog panels
580-1024px (medium)Dialog panels
> 1024px (large)Sidebar panels

Layout size is exposed via the data-layout-size attribute (sm, md, lg) for CSS overrides.

Theming

Override CSS custom properties on the element or any ancestor:

cv-customizer {
--cv-primary: #e11d48;
--cv-radius: 0.5rem;
--cv-panel-width: 300px;
}

See the Theming Guide for the full token reference.

Custom Modules

You can add custom web components as mainbar modules alongside the built-in ones. Define a CustomModule descriptor in the mainBarModules array:

const customizer = document.querySelector('cv-customizer');
customizer.options = {
mainBarModules: [
'text',
'images',
'designs',
{
id: 'quote',
element: 'my-quote-picker',
icon: 'sparkle',
label: 'Get Quote',
},
],
};

CustomModule Interface

PropertyTypeRequiredDescription
idstringYesUnique module ID. Used in disabledModules and initialActiveModule.
elementstringYesCustom element tag name (e.g. 'my-quote-picker'). Must be registered in the DOM.
iconstringNoPhosphor icon name for the mainbar. Default: 'sparkle'.
labelstringNoDisplay label for the mainbar and panel header. Falls back to id.

The custom element will receive a chamevo property set to the ChamevoJS instance, giving it full access to the customizer API.

Distributed Modules

Use registerModule() to mount any module (built-in or custom) into an external DOM element outside the customizer:

const customizer = document.querySelector('cv-customizer');

customizer.addEventListener('cvReady', async () => {
// Mount the text module into a sidebar div
const cleanup = await customizer.registerModule('text', '#my-sidebar');

// Later, unmount the module
cleanup();
});

This is useful for placing modules in custom layouts, separate panels, or other parts of your page.

Framework Integration

React

import { useEffect, useRef } from 'react';
import { defineCustomElements } from '@chamevo/customizer';
import type { CVProduct } from '@chamevo/types';

defineCustomElements();

export function ProductCustomizer({ product }: { product: CVProduct }) {
const ref = useRef<HTMLCvCustomizerElement>(null);

useEffect(() => {
const el = ref.current;
if (!el) return;

const handleReady = async () => {
await el.loadProduct(product);
};

el.addEventListener('cvReady', handleReady);
return () => el.removeEventListener('cvReady', handleReady);
}, [product]);

return (
<cv-customizer
ref={ref}
options={JSON.stringify({ stageWidth: 900, stageHeight: 600 })}
/>
);
}

Vue

<template>
<cv-customizer
ref="customizer"
:options="JSON.stringify(options)"
@cvReady="onReady"
/>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { defineCustomElements } from '@chamevo/customizer';
import type { CVProduct } from '@chamevo/types';

defineCustomElements();

const props = defineProps<{ product: CVProduct }>();
const options = { stageWidth: 900, stageHeight: 600 };
const customizer = ref<HTMLCvCustomizerElement>();

const onReady = async () => {
await customizer.value?.loadProduct(props.product);
};
</script>

Angular

// app.module.ts
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { defineCustomElements } from '@chamevo/customizer';

defineCustomElements();

@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
// ...
})
export class AppModule {}
<!-- customizer.component.html -->
<cv-customizer
#customizer
[attr.options]="optionsJson"
(cvReady)="onReady($event)"
></cv-customizer>
// customizer.component.ts
import { Component, ViewChild, ElementRef } from '@angular/core';

@Component({ /* ... */ })
export class CustomizerComponent {
@ViewChild('customizer') customizerRef: ElementRef;
optionsJson = JSON.stringify({ stageWidth: 900, stageHeight: 600 });

async onReady(e: CustomEvent) {
const { chamevo } = e.detail;
await this.customizerRef.nativeElement.loadProduct(this.product);
}
}

Keyboard Controls

When an element is selected on the canvas:

KeyAction
Arrow keysMove element by 1px
Shift + ArrowMove element by 10px
Delete / BackspaceRemove selected element

Keyboard input is ignored when focus is inside an input field.

Next Steps