Skip to content

JavaScript

TL;DR: Everything JavaScript in one place — architecture, Web Components, the {% javascript %} tag, modern features, and performance.

This section covers everything JavaScript in this theme: how it’s organized, how Web Components are built and structured, how the {% javascript %} tag actually compiles and loads, which modern browser APIs are safe to reach for, and how to keep it fast. If you’re new to any of the ideas used throughout (Custom Elements, CustomEvent, {% javascript %}), this section builds them up from first principles rather than assuming you already know them.

“Where does this JavaScript go — global.js or {% javascript %}?” See JavaScript Architecture. Global JS for things every page needs; {% javascript %} for one section or block.

“Should I use the simple Web Component pattern or the advanced Component/refs pattern?” See Web Components: Two Patterns. Default to simple; adopt the advanced pattern once repeated boilerplate across many components makes it worth the setup cost.

“Why did my listener fire twice after editing in the theme editor?” See Custom Element Lifecycle & Progressive Enhancement and Theme Editor & Storefront Events. A connectedCallback without a matching disconnectedCallback cleanup stacks duplicate listeners across reconnects.

“Can I put {{ product.title }} inside a {% javascript %} tag?” No — see JavaScript in Shopify. Liquid isn’t rendered inside {% javascript %}. Pass data through a data-* attribute instead.

“Do I need a library for this?” Probably not — see Modern JavaScript Features for what’s natively available, and Third-Party Libraries for the decision framework if it turns out you do.

PageCovers
JavaScript Architecture: Global vs. Scoped, State & Eventsglobal.js vs. {% javascript %}, Web Components as the default pattern, the DOM-as-source-of-truth state hierarchy, CustomEvent for communication
Custom Element Lifecycle & Progressive EnhancementA full lifecycle walkthrough, why connectedCallback and not the constructor, progressive enhancement, and when a shared store is actually justified
Web Components: Two PatternsThe simple pattern vs. Horizon’s advanced refs/declarative on:* event pattern, naming rules, Shadow DOM, accessibility
JavaScript in Shopify: the {% javascript %} Tag & Asset ScriptsExactly how {% javascript %} compiles, concatenates, and loads, and how it compares to an asset script
Theme Editor & Storefront Eventsshopify:section:load and the rest of the theme editor’s JS events, and how they interact with a component’s own lifecycle
Modern JavaScript FeaturesIntersectionObserver, ResizeObserver, AbortController, structuredClone, <dialog>, the Popover API — all Baseline widely available, no polyfill needed
JavaScript PerformanceDeferring and scoping scripts, avoiding layout thrashing, debouncing/throttling, and preventing memory leaks across theme editor sessions

Native ES modules and Web Components, no bundler, no framework. Every page in this section is really just working out the consequences of that one sentence in a specific situation.

  • Start with JavaScript Architecture if you’re new to this section. Every other page assumes you already know the global-vs-scoped split and the state/events model it sets up.
  • Default to the simple Web Component pattern and native browser APIs. Reach for the advanced Component pattern or a third-party library only once there’s a real, repeated cost to not having one.
  • Treat the theme editor as a first-class environment your JS runs in, not an edge case. Test every interactive component by editing it repeatedly, not just by loading the page once.
  • Style Guides: Liquid, clean code, and third-party library guidance that sits alongside this section
  • CSS: the CSS-side equivalent of this section’s depth and structure
  • Performance and Accessibility: the theme-wide performance and accessibility strategy this section’s JS-specific pages plug into