
The Shopify ecosystem is undergoing a significant technical evolution. One of the most important shifts for frontend developers is Shopify’s decision to rebuild its entire Polaris design system on a foundation of Web Components.
This move is a strong signal of the future of frontend development on the platform. Web Components—a suite of browser-native technologies—allow us to create encapsulated, reusable, and framework-agnostic UI elements.
While Polaris provides a fantastic library of components for app UIs, the same technology can be leveraged by theme developers to build our own custom, modular components. This is a powerful technique for creating a more maintainable, scalable, and modern theme architecture.
Let’s explore why you should consider using web components in your next theme project and how to build a simple one from scratch.
Why Web Components in a Liquid Theme?
At first glance, it might seem like overkill. We already have snippets and sections, so why introduce a new layer of abstraction?
Web Components solve several long-standing challenges in theme development:
-
True Encapsulation (Scoped CSS and JS): Styles and scripts within a web component are “scoped” to that component. This means the CSS for your custom
<product-card>
component will never accidentally leak out and affect other elements on the page. This is a massive win for maintainability and avoiding style conflicts, especially in large themes or when working with multiple developers. -
Framework Agnostic and Future-Proof: Web Components are a web standard, not a third-party library. They work in all modern browsers without any dependencies. This means your components are incredibly robust and won’t break when you update a library or change your theme’s JavaScript framework.
-
Clean, Semantic Markup: They allow you to replace generic
<div>
soup with clean, semantic, and self-documenting custom HTML tags. Your Liquid code becomes much more readable:Before:
{% render 'product-card', product: product %}
After:
<product-card data-product-json="{{ product | json }}"></product-card>
Building Your First Web Component: A <product-card>
Let’s build a simple product card component that takes a product object as a JSON string and renders itself.
Step 1: Define the Custom Element Class
In your theme’s JavaScript, you’ll define a class that extends HTMLElement
. This class will control the logic and rendering of your component.
// in your theme's main JS file or a dedicated components file
class ProductCard extends HTMLElement {
constructor() {
super();
// Parse the product data passed as an attribute
const productData = JSON.parse(this.getAttribute('data-product-json'));
// Create a shadow DOM for encapsulation
this.attachShadow({ mode: 'open' });
// Define the component's internal HTML and CSS
this.shadowRoot.innerHTML = `
<style>
/* These styles are scoped to this component! */
:host {
display: block;
border: 1px solid #eee;
border-radius: 8px;
overflow: hidden;
}
img {
max-width: 100%;
}
.content {
padding: 1rem;
}
h3 {
margin: 0 0 0.5rem;
}
</style>
<div class="product-card-container">
<img src="${productData.featured_image}" alt="${productData.title}">
<div class="content">
<h3>${productData.title}</h3>
<p>${productData.price / 100}</p>
</div>
</div>
`;
}
}
// Register the custom element with the browser
customElements.define('product-card', ProductCard);
Breaking Down the Code:
class ProductCard extends HTMLElement
: This is the standard way to define a custom element.this.getAttribute('data-product-json')
: We’re getting the product data from adata-
attribute on our custom tag.this.attachShadow({ mode: 'open' })
: This is the key to encapsulation. It creates a “Shadow DOM,” a hidden DOM tree where our component’s styles and markup live, isolated from the rest of the page.<style>
block: The CSS inside this block will only apply to the elements within this component.customElements.define('product-card', ProductCard)
: This tells the browser to associate the<product-card>
HTML tag with ourProductCard
class.
Step 2: Use the Component in Your Liquid
Now, you can use your new component anywhere in your theme with a clean, simple tag.
{% for product in collection.products %}
<product-card data-product-json="{{ product | json | escape }}"></product-card>
{% endfor %}
Notice that we pass the entire product
object as a JSON string. The escape
filter is crucial here to ensure the JSON string is correctly formatted within the HTML attribute.
Final Thoughts: A More Modern Approach to Theme Development
While it requires a bit more JavaScript than a traditional Liquid snippet, building with Web Components brings a level of modularity, encapsulation, and maintainability that was previously difficult to achieve in Shopify themes.
As Shopify itself doubles down on this technology, learning to build your own custom elements is a forward-thinking skill that will prepare you for the future of theme development. Start small with a simple component, and you’ll quickly see the power of this modern, browser-native approach.
❓ What component in your theme would be the best candidate to be rebuilt as a Web Component?