Skip to content

Storefront behavior

What the merchant doesn’t see — what customers actually see and do. This page summarizes widget behavior on the storefront.

Widgets are injected via Theme App Extension. One widget.js (~134 KB raw / 39 KB gzipped) + all merchant widget configs (injected to window.__TTB_BLOCKS__ in Liquid).

Two scan modes run in parallel:

  • Manual — DOM-first: find .ttb-block-wrapper[data-widget-id] → look up __TTB_BLOCKS__[id] → mount (used by Custom Position mode)
  • Auto — config-first: iterate __TTB_BLOCKS__ for placement.mode === "auto" widgets → cascade through DOM anchors → create wrapper and mount

Mount triggers: DOMContentLoaded + body MutationObserver + 5s setInterval safety net.

  • Desktop / mobile breakpoint: default @media (max-width: 768px) (config.breakpoint)
  • Same widget switches fields by viewport width: icon size / spacing / font size etc. all have desktop + mobile value pairs
  • Editor’s desktop / mobile toggle is postMessage-driven; storefront uses real @media query

Image: same widget rendered in desktop vs mobile

Coupon — click copy + auto-apply checkout

Section titled “Coupon — click copy + auto-apply checkout”

i18n hint: “Copied to clipboard and applied to checkout.”

Behavior:

  • Customer clicks coupon → auto-copy to clipboard (toast: “Copied!”)
  • Simultaneously triggers fetch("/discount/CODE", { redirect: "manual" }) to auto-apply
  • If the discount code exists in Shopify Discounts → applied to next checkout

See Make coupon work.

  • Trigger: Click / Hover (merchant choice)
  • Content: title + description + link (optional)
  • Close: click outside / Esc

Image: popover trigger and close

  • widget.js reads window.Shopify.locale (strips region suffix, e.g. en-USen)
  • Looks up translation in config.locales[locale]
  • Not found → falls back to default language

Before mounting, the evaluator runs all 9 dimensions of config.visibility. Only when all pass → mount. Any dimension fails → no mount, diagnostic state writes to window.__TTB_DIAGNOSTICS__[widgetId].

The new version supports collect-all trace: merchants can see all failing dimensions at once, no need to fix one at a time.

No Shadow DOM — theme CSS intentionally cascades in. Isolation: per-instance double-class selectors (e.g. .widget-text.heading-XxYy), styles inline-injected next to each instance.

  • One widget.js cached by Cloudflare CDN
  • Doesn’t block theme first paint (async / defer load)
  • Bundle size: ~134 KB raw / 39 KB gzipped (incl. visibility 8 matchers + auto-placement 65-theme rules + Label Storefront API + multi-axis bundle, etc.)
  • Real perf signals: longtask / parse time / first-paint, not raw bytes alone
  • z-index: not set by default, inherits from mount point
  • Font: defaults to font-family: inherit, auto-follows theme; merchant can override in editor
  • Namespace: CSS selectors all .widget- prefixed; class names with per-instance hash for isolation
  • All interactive elements (buttons, coupon click, popover triggers) support keyboard navigation
  • aria-label / aria-describedby added as needed
  • Color contrast determined by merchant’s color scheme (merchant should self-check)
  • Respects prefers-reduced-motion (system-level reduced motion → widget animations auto-degrade)
BrowserMin version
Chrome88+
Safari14+
Firefox85+
iOS Safari14+
Android Chrome88+
  • Widgets are client-side rendered (CSR); content doesn’t participate in search engine indexing
  • For SEO-valuable content (e.g. key marketing copy), keep it in theme-native HTML, not widgets
  • Google etc. crawlers partially execute JS, may catch some rendered output, but not guaranteed

After each widget mount:

EventTriggerData
impressionWidget mounted (non-designMode)widget_id
click<a> / <button> click within widgetwidget_id, type (button / badge / brand / coupon / payment)
errorMount failed or render errorwidget_id, code, message (200 char truncated)

Merchant can see aggregated impression / click data (last 90 days) in Widget list.

widget.js is a config → DOM pure function. Admin editor preview and storefront rendering share the same widget.js, so preview = production. What the merchant sees in the editor is what ships.

Label widget (Product image overlay) has additional behavior on collection pages:

  • Calls Shopify Storefront API to batch-fetch productData (per-card mount)
  • Same image host id deduplication (multiple widgets don’t double-mount on same image)
  • Nested <a> detection fallback — when card image is inside <a>, chip uses click handler + preventDefault instead of nesting <a>

See Quick start (Product image overlay).