Purpose#
The ProductComparisonButton reusable renders a toggle button that adds/removes a product from the comparison list. It also shows toast feedback and disables itself when the comparison list has reached the maximum size.Where it's rendered#
This reusable is rendered from:Reusables/ProductGridItem/Default.liquid
{% render 'Reusables\\ProductComparisonButton\\Default', id: product.id, title: product.title %}
Reusables/ProductListItem/Default.liquid
{% render 'Reusables\\ProductComparisonButton\\Default', id: product.id, title: product.title %}
Components/ProductSingle/Default.liquid
{% render 'Reusables\\ProductComparisonButton\\Default', id: product.id, title: product.title %}
Product id used as the comparison list key.
Human-readable product title used for the button title / aria-label.
Template behavior (Liquid + Alpine)#
The button is rendered only when the feature flag is enabled:
GlobalData.Settings.ShowProductComparison
It binds Alpine using a factory:
x-data="productcomparisonbuttonreusabledefault.initComponent('{{ id }}')"
The button uses a toggled state class:
:class="` ${productExists ? 'active' : ''}`"
The template binds :disabled="isDisabled".
The JS sets isDisabled when the list is full and the current product is not already in the list.
On click it calls handleClick(...) and passes translated strings (and an HTML snippet that links to /comparison).
Data contract (JS runtime)#
Model shape (storefront example)#
Example (from a real product model dump) keeping only the relevant fields used by this reusable:{
"id": "Product Id",
"title": "Sample title"
}
Runtime data#
Stored in (and retrieved from) productcomparisondefault.
Maximum supported size is 8 items.
The button listens for the comparisonListUpdated event to refresh its state.
It also listens for the storage event when the comparisonSync key changes.
JavaScript#
Global object#
Global object: productcomparisonbuttonreusabledefaultx-data="productcomparisonbuttonreusabledefault.initComponent('<productId>')"
initComponent(id)#
Returns an Alpine component instance bound to a specific product id.
Key behaviors / edge casesThe returned object holds local state (productExists, isListFull, isDisabled) derived from the comparison list.
Keep the id stable (should be the product id). If you pass a variant id instead, you may end up comparing variants instead of products.
init()#
Loads the comparison list via productcomparisondefault.getList().
comparisonListUpdated (custom event)
storage event for cross-tab sync (comparisonSync)
Key behaviors / edge casesIf the list changes in another tab, the storage listener keeps the button state in sync.
Don’t remove the storage listener if you want comparison to work reliably across multiple tabs.
updateState()#
productExists: whether the current id is in the list
isListFull: whether list length is >= 8
isDisabled: list is full AND the product is not in the list
Key behaviors / edge casesA product that is already in the list stays enabled even when the list is full (so it can be removed).
handleClick(addedProduct, removedProduct, addedProductAndListFull, extraText)#
Toggles the product in the list.
Persists the updated list via productcomparisondefault.setList(this.list).
Dispatches comparisonListUpdated with a payload { id, timestamp }.
Displays toast feedback using $store.toast:When removing: uses removedProduct.
uses addedProductAndListFull if list becomes exactly 8
otherwise uses addedProduct
In most cases it includes extraText, which contains the link to /comparison.
Key behaviors / edge casesWhen the list becomes empty after removing, it uses a toast call without extraText.
extraText is HTML (includes an <a>). Ensure it is escaped/sanitized at the point it is constructed in Liquid.
Global Alpine stores#
add(...) is called to show user feedback after add/remove.
Dependencies#
Liquid: Reusables/ProductComparisonButton/Default.liquid
JS: Reusables/ProductComparisonButton/Default.js
Translations: Reusables/ProductComparisonButton/Default.json
Global JS object: productcomparisondefault (list storage helpers)
Alpine store: $store.toast
Page route: /comparison (used in the toast extra text)
Notes#
Rendering is gated by GlobalData.Settings.ShowProductComparison.
The comparison limit is hard-coded to 8 in updateState().
The button title / aria-label includes title passed from Liquid, so send a human-readable product title.
Modified at 2026-04-14 13:18:56