Developer docs menu

Widget reference

Every attribute the quotometry-quote element takes, every event it emits, its methods, the JavaScript mount API, theming variables and CSP.

This page is the reference for the <quotometry-quote> element: what you can set on it, what it tells you, what you can ask it to do, how to theme it and which content security policy it needs. For a first installation, start with the quick start.

The widget is a custom element that renders into a shadow root. Your CSS cannot reach into it and its CSS cannot reach out. It brings its own typeface (Inter) so it looks the same on every page; set --quotometry-font to use yours.

The widget never throws into your page. A configuration mistake, a network failure or a bad file becomes a message inside the widget, and your page keeps working.

Attributes

AttributeTypeRequiredDefaultDescription
widgetstringYes-The widget key, wgt_live_… or wgt_test_….
apiURLNohttps://api.quotometry.appBase URL of the buyer API. Set it on a regional or self-hosted deployment.
modeinline | modal | drawerNoinlineinline: the element is the widget. modal: a centred dialog. drawer: a panel that slides in. A modal or drawer starts closed and renders nothing until it is opened.
localeBCP 47 tagNoThe page’s lang, then the browser’s languageThe language of the widget’s own text. A non-English locale is fetched alongside the first request; English is the fallback for any missing string.
themelight | darkNoYour widget’s setting, else the systemThe colour scheme the widget starts in. Buyers can switch with the light / dark button in the widget’s top bar, and their choice is remembered on their device. Your primary colour, and any colour set in your page’s CSS, still apply in both schemes (Theming).
fontsoffNo-Set to off to stop the widget asking for its own typeface. It then uses the system sans-serif. Useful under a strict content security policy.
badgestandard | minimal | hiddenNostandardThe “Powered by” badge. Whether minimal and hidden are allowed depends on your plan.
siteURLNohttps://quotometry.appWhere the badge links.
configstringNo-Pre-selects one configuration id, for a widget that offers several.
materialstringNo-Pre-selects a material id.
customer-tokenstringNo-A signed token identifying a signed-in buyer. See customer tokens.
customer-emailstringNo-An e-mail address you already know, when you do not sign tokens. It is a claim, not proof.
return-urlURLNo-Where to send the buyer after a cart hand-off by redirect.
metadataJSON stringNo-Carried onto the quote and order untouched, and echoed in order.created: your cart id, campaign or line reference.

Attributes are live: change one and the widget re-renders. Changing widget starts a new buyer session.

Methods

MethodDescription
open()Shows a modal or drawer widget. Does nothing for an inline widget.
close()Hides it again. Escape and a click on the backdrop do the same.
reset()Throws the buyer’s draft away and starts over at the drop zone.
setCustomer({ email, token })Identifies the buyer: token sets customer-token, email sets customer-email. Omit one to clear it.
const el = document.querySelector('quotometry-quote');
el.open();
el.setCustomer({ token: customerToken });
el.reset();

A single-page app should call reset() when the buyer navigates away from a page that had a widget on it.

Events

Every event is a CustomEvent on the element. Events bubble and are composed, so document.addEventListener works too.

EventFires whendetail
quotometry:quote-updatedA re-price finishes.{ totals, state, lines }
quotometry:cart-addThe buyer presses your cart button.{ quote_id, number, totals, signature }
quotometry:quote-savedThe buyer saves their quote to come back to.{ quote_id, url, emailed }
quotometry:order-submittedAn order is placed through Quotometry.{ id, number, status, totals }
quotometry:rfq-submittedThe buyer sends a request for quote that a person must answer.{ id, number }
el.addEventListener('quotometry:cart-add', async (e) => {
  await fetch('/cart/add', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ quotometry_quote: e.detail.quote_id, quotometry_sig: e.detail.signature }),
  });
  window.location.href = '/cart';
});

signature proves that the quote id came from your widget and not from the address bar. Your server checks it with the shop’s shared secret before it adds anything to the cart. It is absent when the shop has not created a shared secret. See how to check it.

Opening from a button

For a product page that should not download the widget until someone asks for it:

<button data-quotometry-open="wgt_live_YOURKEY" data-quotometry-mode="drawer" data-quotometry-material="petg">
  Get an instant price
</button>

Every data-quotometry-* attribute on the button becomes the matching widget attribute, and the widget opens as a modal unless data-quotometry-mode says otherwise. The loader starts downloading the widget when the pointer moves over the button or the button gets focus, so the click still feels instant, and a page whose button is never pressed never downloads it.

Mounting from JavaScript

The loader defines a global Quotometry object:

const handle = await Quotometry.mount('#quote', { widget: 'wgt_live_YOURKEY', mode: 'inline' });
const off = handle.on('quote:updated', (detail) => render(detail));
handle.open();
// later
off();
FunctionDescription
Quotometry.mount(target, options)Creates a widget inside target (a selector or an element) and resolves with a handle once the widget is loaded. Option names become attributes; customerToken and customer-token both work.
Quotometry.get(target)Resolves with a handle for a <quotometry-quote> that is already in the page.
Quotometry.open(target)Opens a mounted widget, or the widget a data-quotometry-open button refers to.
Quotometry.preload()Downloads the widget without showing anything, to warm the cache.

A handle has on(event, handler), which returns a function that removes the listener, plus open(), close(), reset(), setCustomer() and element. It is a thin wrapper: the same events reach addEventListener on handle.element, and on accepts both quote:updated and quotometry:quote-updated.

Theming

Set the --quotometry-* custom properties on the element itself (quotometry-quote { … }). They cross the shadow boundary because custom properties inherit; nothing else does. If you set them on an ancestor such as :root, the widget’s own defaults on the element win over them. The exception is --quotometry-color-primary, which has no default and is therefore read from an ancestor too.

VariableDefaultWhat it colours
--quotometry-color-primaryNoneYour brand colour, set here or in the console. It becomes the highlight and the main button, in light and dark.
--quotometry-color-on-primary#FFFFFFText on the primary colour.
--quotometry-color-highlight#A16207 (dark: #F5B83D)Every selected state: the chosen part, option, tool and tab, and the focus ring.
--quotometry-color-on-highlight#FFFFFF (dark: #17181B)Text on a selected option filled with the highlight.
--quotometry-color-cta / --quotometry-color-on-cta#161B22 / #FFFFFF (dark: #ECEDEF / #17181B)The main button: Continue, Add to cart, Order.
--quotometry-color-canvas#F1F2F4 (dark: #0F1012)The background behind the three panels.
--quotometry-color-accent#FF6A1AAccents.
--quotometry-color-surface#FFFFFFPanels.
--quotometry-color-surface-2#F6F8FAInsets and the viewer’s backing.
--quotometry-color-text#161B22Body text.
--quotometry-color-muted#57606ASecondary text.
--quotometry-color-border#D0D7DERules and outlines.
--quotometry-color-success / -warning / -danger / -review-State colours.
--quotometry-radius10pxCorner radius.
--quotometry-fontInter, then the system sans-serifThe typeface. Set it to inherit or a font stack to use your page’s font.
--quotometry-density44pxMinimum control height. Lower it for a dense B2B interface; 44px is the comfortable touch target.
--quotometry-viewer-bgA light gradient (dark: a charcoal one)The background behind the 3D model.
--quotometry-heat-1 … --quotometry-heat-4-The colour ramp of the wall-thickness heat map.

The part is drawn in the colour the buyer picked, or in neutral grey before they pick one.

quotometry-quote {
  --quotometry-color-primary: #0f766e;
  --quotometry-radius: 2px;
  --quotometry-density: 36px;
}

Most shops set the theme in the console instead, on the widget’s Look tab. It stores the same variables against the widget and applies them before first paint, with a preview in both schemes and a contrast check for every pair of colours.

How the two sources combine:

  • A primary colour works the same from either place: it becomes the highlight and the main button in light and dark, with --quotometry-color-on-primary (white unless you set it) as their text.
  • A colour set in your page’s CSS wins over the console’s.
  • Colours set in the console are the light scheme’s. A buyer who switches to dark gets Quotometry’s dark surfaces and text, with your primary colour carried over.
  • Colours set in your page’s CSS apply in both schemes. To colour the highlight and the main button differently, set --quotometry-color-highlight and --quotometry-color-cta yourself.

Content Security Policy

The widget is tested against this policy. A host page in our compatibility matrix loads the widget under it and fails on any violation:

default-src 'self';
script-src  'self' https://cdn.quotometry.example;
connect-src 'self' https://api.quotometry.example;
img-src     'self' blob: data:;
style-src   'self' 'unsafe-inline';
worker-src  'self' blob:;

Replace the example hosts with the ones in your widget’s install snippet. Five things are worth knowing:

  • style-src 'unsafe-inline' is required. The widget injects its stylesheet as a <style> element inside its shadow root, and style-src governs that even though it is not in your document.
  • blob: is for the viewer, which builds the mesh and its preview image in memory. Without it the buyer gets a price but no picture of their part.
  • Files go straight to Quotometry’s file store when they can. If your policy (or the buyer’s network) does not let the browser reach the store, the widget sends the file through the API instead. The policy above therefore works as it is; the blocked attempt shows in the browser console as a refused connection, and the upload continues through the API.
  • The typeface is optional. The widget loads Inter from beside its own script. Add font-src https://cdn.quotometry.example to allow it, or set fonts="off"; without either, the widget uses the system sans-serif and the layout is unchanged.
  • frame-ancestors belongs in a header. Browsers ignore it in a <meta> tag, so if you paste a policy into a <meta> element, that directive does nothing.

If your install snippet loads the script from your widget’s address on the API (/w/wgt_live_…/loader.js), add the API host to script-src as well.

There is no inline script and no eval anywhere in the widget, which is why script-src needs nothing more than the hosts the script comes from.

Next steps