Components
Components are versioned agent responses. confirm@1, choice@1, and card@1 are shared: the embed renders them automatically. Do not register them. Host components such as productPreview@1 need tiketoChat("component", ...); Tiketo supplies the enabled name and schema.
Since v0.3.0, embed-owned labels use the launch snapshot of context.locale: cs* selects Czech, and every other/missing value selects English. This covers generic fallback text and confirm Yes/No buttons. Questions, choices, card copy, and contract fallbackText come from the agent payload and are not translated.
Renderer contract
Section titled “Renderer contract”The renderer receives:
element: an empty mount inside the widget’s open shadow DOM.payload: data validated against the component contract.{ reply }: a one-shot validated reply function.
The agent creates requestId in its trace payload. The embed validates it, requires a reply to echo the same value, and de-duplicates valid replies; reply() handles those checks for you. Do not generate a replacement. Only the first valid reply is sent, including after conversation history is restored.
Create nodes through element.ownerDocument. Host-page stylesheets do not cross the widget’s shadow boundary, so use inline styles, CSS-in-JS attached to this mount, or append a scoped <style> element inside it. Do not query or style unrelated widget internals.
Return a cleanup function that removes listeners, timers, injected styles, and framework roots. The widget calls it when that response unmounts. Register before the response is first rendered; if history restores a host component before your renderer mounts, that response shows its fallback instead.
Complete productPreview example
Section titled “Complete productPreview example”tiketoChat("component", "productPreview", function (element, payload, api) { var ownerDocument = element.ownerDocument; var card = ownerDocument.createElement("article"); var image; var cleanups = []; var replied = false;
card.style.cssText = "display:grid;gap:12px;padding:16px;border:2px solid #000050;border-radius:16px";
if (payload.imageUrl) { image = ownerDocument.createElement("img"); image.src = payload.imageUrl; image.alt = payload.name; image.width = 640; image.height = 360; image.loading = "lazy"; image.style.maxWidth = "100%"; var removeBrokenImage = function () { image.remove(); }; image.addEventListener("error", removeBrokenImage); cleanups.push(function () { image.removeEventListener("error", removeBrokenImage); }); card.appendChild(image); }
var heading = ownerDocument.createElement("h3"); heading.textContent = payload.name; card.appendChild(heading);
var productId = ownerDocument.createElement("p"); productId.textContent = "Product: " + payload.productId; card.appendChild(productId);
var price = ownerDocument.createElement("p"); price.textContent = new Intl.NumberFormat(ownerDocument.documentElement.lang || "en", { style: "currency", currency: payload.currency, }).format(payload.price); card.appendChild(price);
var buttons = []; payload.variants.forEach(function (variant) { var button = ownerDocument.createElement("button"); button.type = "button"; button.textContent = "Choose " + variant; var choose = function () { if (replied) return; replied = true; buttons.forEach(function (item) { item.disabled = true; }); api.reply({ requestId: payload.requestId, productId: payload.productId, variant: variant, }); }; button.addEventListener("click", choose); cleanups.push(function () { button.removeEventListener("click", choose); }); buttons.push(button); card.appendChild(button); });
element.appendChild(card); return function cleanup() { cleanups.forEach(function (remove) { remove(); }); element.replaceChildren(); };});price is a number and currency is CZK or EUR; format them with Intl.NumberFormat using your page locale. imageUrl, when present, is already restricted to HTTPS or a safe root-relative path. Explicit dimensions reduce layout shift, lazy loading avoids needless work, and the error handler removes a broken image. The host owns this component’s visual design.
Always use textContent or framework escaping for payload data and real accessible controls.
Shared card shape
Section titled “Shared card shape”card@1 requires title and text. imageUrl and link are optional, but when link is present it must contain both link.url and link.label. The generated catalog uses “yes, when parent is present” for this conditional requirement.
Images, links, and fallback
Section titled “Images, links, and fallback”Image/link fields accept absolute https: URLs or safe root-relative paths such as /images/product.svg. Protocol-relative URLs, traversal segments, other protocols, and malformed URLs are rejected. Root-relative component assets resolve on the host website.
When a component is unknown, disabled, unregistered, invalid, or its renderer throws, the widget shows payload fallbackText where valid, otherwise the locale-specific generic fallback. Supply meaningful fallback copy when designing a contract with Tiketo.