Storefront behavior
What the merchant doesn’t see — what customers actually see and do. This page summarizes widget behavior on the storefront.
Loading mechanism
Section titled “Loading mechanism”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__forplacement.mode === "auto"widgets → cascade through DOM anchors → create wrapper and mount
Mount triggers: DOMContentLoaded + body MutationObserver + 5s setInterval safety net.
Responsive
Section titled “Responsive”- 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
@mediaquery
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.
Badge popover
Section titled “Badge popover”- Trigger: Click / Hover (merchant choice)
- Content: title + description + link (optional)
- Close: click outside / Esc
Image: popover trigger and close
Multi-language switch
Section titled “Multi-language switch”widget.jsreadswindow.Shopify.locale(strips region suffix, e.g.en-US→en)- Looks up translation in
config.locales[locale] - Not found → falls back to default language
Visibility evaluation
Section titled “Visibility evaluation”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.
CSS isolation
Section titled “CSS isolation”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.
Loading performance
Section titled “Loading performance”- One
widget.jscached 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
Theme conflict avoidance
Section titled “Theme conflict avoidance”- 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
Accessibility
Section titled “Accessibility”- 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)
Browser support
Section titled “Browser support”| Browser | Min version |
|---|---|
| Chrome | 88+ |
| Safari | 14+ |
| Firefox | 85+ |
| iOS Safari | 14+ |
| Android Chrome | 88+ |
- 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
Analytics tracking
Section titled “Analytics tracking”After each widget mount:
| Event | Trigger | Data |
|---|---|---|
impression | Widget mounted (non-designMode) | widget_id |
click | <a> / <button> click within widget | widget_id, type (button / badge / brand / coupon / payment) |
error | Mount failed or render error | widget_id, code, message (200 char truncated) |
Merchant can see aggregated impression / click data (last 90 days) in Widget list.
Render consistency
Section titled “Render consistency”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 special behavior
Section titled “Label widget special behavior”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>