In this customization, we're adding product variant swatches to the collection page.
We did a previous video on this, but it was a simplified version where the swatches didn’t change the image, and instead went to the product page.
But now, in this tutorial, we have a solution where you can click on the individual swatches to change the product image directly in the collection.
As far as we know this is a unique solution, so I hope this helps you guys.
Compatible Themes: This code should work on all free Shopify themes (Dawn, Refresh, Craft, Studio, Publisher, Crave, Origin, Taste, Colorblock, Sense, Ride, Spotlight).
Links:
- NEW version of variant swatches for your Collection Page
- Add variant swatches to your Product page
- Add variant swatches to your Collection Filters
1. Turn off “Show second image on hover” in theme editor
2. Create New Snippet And Asset Files
Create liquid snippet card-variant-swatch-custom.liquid
{% comment %}
Description:
Renders product variant swatch options for collection page based on image URLs.
Defaults to buttons if no image URL is present.
Accepts:
- product: {Object} product object.
- option: {Object} current product_option object.
- variant_images_data: list of variant images
- lazy_load_all_variants: lazy load all variant images for immediate availability when clicking swatches
Usage:
{% render 'variant-swatch-collection',
product: product,
option: option,
variant_images_data: variant_images_data,
lazy_load_all_variants: lazy_load_all_variants
%}
{% endcomment %}
{% assign base_store_files_url = '//YOUR-SHOP-NAME.myshopify.com/cdn/shop/files/' %}
{% assign product_form_id = 'product-form-' | append: product.id %}
{% for value in option.values %}
{% assign variant_image_url = nil %}
{% assign variant_id = nil %}
{% assign option_disabled = true %}
{% assign first_match_found = false %}
{% for variant in product.variants %}
{% if first_match_found == false %}
{% case option.position %}
{% when 1 %}
{% if variant.option1 == value %}
{% assign variant_image_url = variant.featured_media | img_url: '300x300' %}
{% assign variant_id = variant.id %}
{% if lazy_load_all_variants %}
<!-- Preload image -->
<img
src="{{ variant_image_url }}"
alt="{{ variant.title }}"
style="display: none;"
loading="lazy"
data-variant-id="{{ variant.id }}"
width="{{ variant.featured_media.width }}"
height="{{ variant.featured_media.height }}">
{% endif %}
{% if variant.available %}
{% assign option_disabled = false %}
{% endif %}
{% assign first_match_found = true %}
{% endif %}
{% when 2 %}
{% if variant.option2 == value and variant.option1 == product.selected_or_first_available_variant.option1 %}
{% assign variant_image_url = variant.featured_media | img_url: '300x300' %}
{% assign variant_id = variant.id %}
{% if lazy_load_all_variants %}
<!-- Preload image -->
<img
src="{{ variant_image_url }}"
alt="{{ variant.title }}"
style="display: none;"
loading="lazy"
data-variant-id="{{ variant.id }}"
width="{{ variant.featured_media.width }}"
height="{{ variant.featured_media.height }}">
{% endif %}
{% if variant.available %}
{% assign option_disabled = false %}
{% endif %}
{% assign first_match_found = true %}
{% endif %}
{% when 3 %}
{% if variant.option3 == value and variant.option1 == product.selected_or_first_available_variant.option1 and variant.option2 == product.selected_or_first_available_variant.option2 %}
{% assign variant_image_url = variant.featured_media | img_url: '300x300' %}
{% assign variant_id = variant.id %}
{% if lazy_load_all_variants %}
<!-- Preload image -->
<img
src="{{ variant_image_url }}"
alt="{{ variant.title }}"
style="display: none;"
loading="lazy"
data-variant-id="{{ variant.id }}"
width="{{ variant.featured_media.width }}"
height="{{ variant.featured_media.height }}">
{% endif %}
{% if variant.available %}
{% assign option_disabled = false %}
{% endif %}
{% assign first_match_found = true %}
{% endif %}
{% endcase %}
{% endif %}
{% if variant_image_url %}
{% for item in variant_images_data %}
{% if item.variant_value == value %}
{% assign variant_filename = item.variant_swatch %}
{% unless variant_filename == blank %}
{% assign variant_image_url = base_store_files_url | append: variant_filename %}
{% endunless %}
{% break %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
<div class="collection-product-card__swatch">
<input
type="radio"
id="collection-{{ section.id }}-{{ product.id }}-{{ option.position }}-{{ forloop.index0 }}"
name="collection-{{ section.id }}-{{ product.id }}-{{ option.name }}"
value="{{ value | escape }}"
form="{{ product_form_id }}"
data-section-id="{{ section.id }}"
data-product-id="{{ product.id }}"
data-variant-id="{{ variant_id }}"
data-image-url="{{ variant_image_url }}"
{% if option.selected_value == value %}
checked
{% endif %}
{% if option_disabled %}
class="disabled"
{% endif %}
>
<label for="collection-{{ section.id }}-{{ product.id }}-{{ option.position }}-{{ forloop.index0 }}" style="background-image: url('{{ variant_image_url }}');">
<span class="visually-hidden">{{ 'products.product.variant_sold_out_or_unavailable' | t }}</span>
</label>
</div>
{% endfor %}
<style>
.collection-product-card__swatch {
display: inline-block;
margin-top: 5px;
margin-right: 5px;
}
.collection-product-card__swatch input {
display: none;
}
.collection-product-card__swatch label {
display: block;
width: 25px; /* Adjust for desired swatch size */
height: 25px; /* Adjust for desired swatch size */
border: 1px solid #777;
border-radius: 50%; /* 50% for circle, 0% for square */
background-size: cover;
cursor: pointer;
transition: border-color 0.3s ease;
}
.collection-product-card__swatch label:hover {
border-color: #333 !important;
}
.collection-product-card__swatch input:checked + label {
border-color: #333;
border-width: 2px;
box-shadow: inset 0 0 0 1px #fff;
}
.collection-product-card__swatch input.disabled + label {
opacity: 0.5;
position: relative;
}
.collection-product-card__swatch input.disabled + label::after {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: linear-gradient(to bottom right, transparent 45%, rgba(255, 0, 0, 0.6) 50%, transparent 55%);
pointer-events: none;
}
</style>
Create liquid snippet card-product-custom.liquid (v11, v12)
{% comment %}
Renders a product card
Accepts:
- card_product: {Object} Product Liquid object (optional)
- media_aspect_ratio: {String} Size of the product image card. Values are "square" and "portrait". Default is "square" (optional)
- image_shape: {String} Image mask to apply to the product image card. Values are "arch", "blob", "chevronleft", "chevronright", "diamond", "parallelogram", and "round". (optional)
- show_secondary_image: {Boolean} Show the secondary image on hover. Default: false (optional)
- show_vendor: {Boolean} Show the product vendor. Default: false
- show_rating: {Boolean} Show the product rating. Default: false
- extend_height: {Boolean} Card height extends to available container space. Default: true (optional)
- lazy_load: {Boolean} Image should be lazy loaded. Default: true (optional)
- show_quick_add: {Boolean} Show the quick add button.
- section_id: {String} The ID of the section that contains this card.
- horizontal_class: {Boolean} Add a card--horizontal class if set to true. Default: false (optional)
- horizontal_quick_add: {Boolean} Changes the quick add button styles when set to true. Default: false (optional)
- placeholder_image: {String} The placeholder image to use when no product exists. Default: 'product-apparel-2' (optional)
Usage:
{% render 'card-product', show_vendor: section.settings.show_vendor %}
{% endcomment %}
{{ 'component-rating.css' | asset_url | stylesheet_tag }}
{{ 'component-volume-pricing.css' | asset_url | stylesheet_tag }}
{% assign variant_option_name = "Color" %} {% comment %}Setting: Variant Option Name To Display On Collection Page{% endcomment %}
{% assign entry_title = "variant-swatch-mapping" %} {% comment %}Setting: Name of Metaobject entry{% endcomment %}
{% assign lazy_load_all_variants = false %} {% comment %}Setting: Turn Lazy Load on or off. true: ON, false: OFF{% endcomment %}
{%- if card_product and card_product != empty -%}
{%- liquid
assign ratio = 1
if card_product.featured_media and media_aspect_ratio == 'portrait'
assign ratio = 0.8
elsif card_product.featured_media and media_aspect_ratio == 'adapt'
assign ratio = card_product.featured_media.aspect_ratio
endif
if ratio == 0 or ratio == null
assign ratio = 1
endif
-%}
{% assign target_entry = nil %}
{% for entry in shop.metaobjects.variant_swatch_map.values %}
{% if entry.title == entry_title %}
{% assign target_entry = entry %}
{% break %}
{% endif %}
{% endfor %}
{% if target_entry %}
{% assign variant_images_data = target_entry.variant_images_json %}
{% else %}
{% assign variant_images_data = nil %}
{% endif %}
<div class="card-product-custom-div" data-section-id="{{ section.id }}" data-lazy-load-all-variants="{{ lazy_load_all_variants }}">
<div class="card-wrapper product-card-wrapper underline-links-hover">
<div
class="
card card--{{ settings.card_style }}
{% if card_product.featured_media %} card--media{% else %} card--text{% endif %}
{% if settings.card_style == 'card' %} color-{{ settings.card_color_scheme }} gradient{% endif %}
{% if image_shape and image_shape != 'default' %} card--shape{% endif %}
{% if extend_height %} card--extend-height{% endif %}
{% if card_product.featured_media == nil and settings.card_style == 'card' %} ratio{% endif %}
{% if horizontal_class %} card--horizontal{% endif %}
"
style="--ratio-percent: {{ 1 | divided_by: ratio | times: 100 }}%;"
>
<div
class="card__inner {% if settings.card_style == 'standard' %}color-{{ settings.card_color_scheme }} gradient{% endif %}{% if card_product.featured_media or settings.card_style == 'standard' %} ratio{% endif %}"
style="--ratio-percent: {{ 1 | divided_by: ratio | times: 100 }}%;"
>
{%- if card_product.featured_media -%}
<div class="card__media{% if image_shape and image_shape != 'default' %} shape--{{ image_shape }} color-{{ settings.card_color_scheme }} gradient{% endif %}" id="ProductCardImage-{{ card_product.id }}"> {% comment %} Code changed here {% endcomment %}
<div class="media media--transparent media--hover-effect">
{% comment %}theme-check-disable ImgLazyLoading{% endcomment %}
<img
srcset="
{%- if card_product.featured_media.width >= 165 -%}{{ card_product.featured_media | image_url: width: 165 }} 165w,{%- endif -%}
{%- if card_product.featured_media.width >= 360 -%}{{ card_product.featured_media | image_url: width: 360 }} 360w,{%- endif -%}
{%- if card_product.featured_media.width >= 533 -%}{{ card_product.featured_media | image_url: width: 533 }} 533w,{%- endif -%}
{%- if card_product.featured_media.width >= 720 -%}{{ card_product.featured_media | image_url: width: 720 }} 720w,{%- endif -%}
{%- if card_product.featured_media.width >= 940 -%}{{ card_product.featured_media | image_url: width: 940 }} 940w,{%- endif -%}
{%- if card_product.featured_media.width >= 1066 -%}{{ card_product.featured_media | image_url: width: 1066 }} 1066w,{%- endif -%}
{{ card_product.featured_media | image_url }} {{ card_product.featured_media.width }}w
"
src="{{ card_product.featured_media | image_url: width: 533 }}"
sizes="(min-width: {{ settings.page_width }}px) {{ settings.page_width | minus: 130 | divided_by: 4 }}px, (min-width: 990px) calc((100vw - 130px) / 4), (min-width: 750px) calc((100vw - 120px) / 3), calc((100vw - 35px) / 2)"
alt="{{ card_product.featured_media.alt | escape }}"
class="motion-reduce"
{% unless lazy_load == false %}
loading="lazy"
{% endunless %}
width="{{ card_product.featured_media.width }}"
height="{{ card_product.featured_media.height }}"
>
{% comment %}theme-check-enable ImgLazyLoading{% endcomment %}
{%- if card_product.media[1] != null and show_secondary_image -%}
<img
srcset="
{%- if card_product.media[1].width >= 165 -%}{{ card_product.media[1] | image_url: width: 165 }} 165w,{%- endif -%}
{%- if card_product.media[1].width >= 360 -%}{{ card_product.media[1] | image_url: width: 360 }} 360w,{%- endif -%}
{%- if card_product.media[1].width >= 533 -%}{{ card_product.media[1] | image_url: width: 533 }} 533w,{%- endif -%}
{%- if card_product.media[1].width >= 720 -%}{{ card_product.media[1] | image_url: width: 720 }} 720w,{%- endif -%}
{%- if card_product.media[1].width >= 940 -%}{{ card_product.media[1] | image_url: width: 940 }} 940w,{%- endif -%}
{%- if card_product.media[1].width >= 1066 -%}{{ card_product.media[1] | image_url: width: 1066 }} 1066w,{%- endif -%}
{{ card_product.media[1] | image_url }} {{ card_product.media[1].width }}w
"
src="{{ card_product.media[1] | image_url: width: 533 }}"
sizes="(min-width: {{ settings.page_width }}px) {{ settings.page_width | minus: 130 | divided_by: 4 }}px, (min-width: 990px) calc((100vw - 130px) / 4), (min-width: 750px) calc((100vw - 120px) / 3), calc((100vw - 35px) / 2)"
alt=""
class="motion-reduce"
loading="lazy"
width="{{ card_product.media[1].width }}"
height="{{ card_product.media[1].height }}"
>
{%- endif -%}
</div>
</div>
{%- endif -%}
<div class="card__content">
<div class="card__information">
<h3
class="card__heading"
{% if card_product.featured_media == null and settings.card_style == 'standard' %}
id="title-{{ section_id }}-{{ card_product.id }}"
{% endif %}
>
<a
href="{{ card_product.url }}"
id="StandardCardNoMediaLink-{{ section_id }}-{{ card_product.id }}"
class="full-unstyled-link"
aria-labelledby="StandardCardNoMediaLink-{{ section_id }}-{{ card_product.id }} NoMediaStandardBadge-{{ section_id }}-{{ card_product.id }}"
>
{{ card_product.title | escape }}
</a>
</h3>
</div>
<div class="card__badge {{ settings.badge_position }}">
{%- if card_product.available == false -%}
<span
id="NoMediaStandardBadge-{{ section_id }}-{{ card_product.id }}"
class="badge badge--bottom-left color-{{ settings.sold_out_badge_color_scheme }}"
>
{{- 'products.product.sold_out' | t -}}
</span>
{%- elsif card_product.compare_at_price > card_product.price and card_product.available -%}
{%- assign difference = card_product.compare_at_price | minus: card_product.price -%}
{%- assign float_difference = difference | times: 1.0 -%}
{%- assign discount_fraction = float_difference | divided_by: card_product.compare_at_price -%}
{%- assign discount_percentage = discount_fraction | times: 100 | round -%}
<span
id="NoMediaStandardBadge-{{ section_id }}-{{ card_product.id }}"
class="badge badge--bottom-left color-{{ settings.sale_badge_color_scheme }}"
>
{% comment %}{{- 'products.product.on_sale' | t -}}{% endcomment %}
{{- discount_percentage }}% OFF
</span>
{%- endif -%}
</div>
</div>
</div>
<h3
class="card__heading{% if card_product.featured_media or settings.card_style == 'standard' %} h5{% endif %}"
{% if card_product.featured_media or settings.card_style == 'card' %}
id="title-{{ section_id }}-{{ card_product.id }}"
{% endif %}
>
<a
href="{{ card_product.url }}"
id="CardLink-{{ section_id }}-{{ card_product.id }}"
class="full-unstyled-link"
aria-labelledby="CardLink-{{ section_id }}-{{ card_product.id }} Badge-{{ section_id }}-{{ card_product.id }}"
>
</a>
</h3>
</div>
</div>
<div class = "collection-product-card__variants">
{%- for card_product_option in card_product.options_with_values -%}
{% if card_product_option.name == variant_option_name %}
{% assign variant_options_images = variant_images_data.value | where: "variant_name", card_product_option.name %}
{% assign special_handling = false %}
{% if variant_options_images.size > 0 %}
{% assign special_handling = true %}
{% endif %}
{% render 'card-variant-swatch-custom', product: card_product, option: card_product_option, variant_images_data: variant_options_images, lazy_load_all_variants: lazy_load_all_variants %}
{% endif %}
{%- endfor -%}
</div>
<div class="card-wrapper product-card-wrapper underline-links-hover">
<div
class="
card card--{{ settings.card_style }}
{% if card_product.featured_media %} card--media{% else %} card--text{% endif %}
{% if settings.card_style == 'card' %} color-{{ settings.card_color_scheme }} gradient{% endif %}
{% if image_shape and image_shape != 'default' %} card--shape{% endif %}
{% if extend_height %} card--extend-height{% endif %}
{% if card_product.featured_media == nil and settings.card_style == 'card' %} ratio{% endif %}
{% if horizontal_class %} card--horizontal{% endif %}
"
style="--ratio-percent: {{ 1 | divided_by: ratio | times: 100 }}%;"
>
<div class="card__content">
<div class="card__information">
<h3
class="card__heading{% if card_product.featured_media or settings.card_style == 'standard' %} h5{% endif %}"
{% if card_product.featured_media or settings.card_style == 'card' %}
id="title-{{ section_id }}-{{ card_product.id }}"
{% endif %}
>
<a
href="{{ card_product.url }}"
id="CardLink-{{ section_id }}-{{ card_product.id }}"
class="full-unstyled-link"
aria-labelledby="CardLink-{{ section_id }}-{{ card_product.id }} Badge-{{ section_id }}-{{ card_product.id }}"
>
{{ card_product.title | escape }}
</a>
</h3>
<div class="card-information">
{%- if show_vendor -%}
<span class="visually-hidden">{{ 'accessibility.vendor' | t }}</span>
<div class="caption-with-letter-spacing light">{{ card_product.vendor }}</div>
{%- endif -%}
<span class="caption-large light">{{ block.settings.description | escape }}</span>
{%- if show_rating and card_product.metafields.reviews.rating.value != blank -%}
{% liquid
assign rating_decimal = 0
assign decimal = card_product.metafields.reviews.rating.value.rating | modulo: 1
if decimal >= 0.3 and decimal <= 0.7
assign rating_decimal = 0.5
elsif decimal > 0.7
assign rating_decimal = 1
endif
%}
<div
class="rating"
role="img"
aria-label="{{ 'accessibility.star_reviews_info' | t: rating_value: card_product.metafields.reviews.rating.value, rating_max: card_product.metafields.reviews.rating.value.scale_max }}"
>
<span
aria-hidden="true"
class="rating-star"
style="--rating: {{ card_product.metafields.reviews.rating.value.rating | floor }}; --rating-max: {{ card_product.metafields.reviews.rating.value.scale_max }}; --rating-decimal: {{ rating_decimal }};"
></span>
</div>
<p class="rating-text caption">
<span aria-hidden="true">
{{- card_product.metafields.reviews.rating.value }} /
{{ card_product.metafields.reviews.rating.value.scale_max -}}
</span>
</p>
<p class="rating-count caption">
<span aria-hidden="true">({{ card_product.metafields.reviews.rating_count }})</span>
<span class="visually-hidden">
{{- card_product.metafields.reviews.rating_count }}
{{ 'accessibility.total_reviews' | t -}}
</span>
</p>
{%- endif -%}
{% render 'price', product: card_product, price_class: '', show_compare_at_price: true %}
{%- if card_product.quantity_price_breaks_configured? -%}
<div class="card__information-volume-pricing-note">
<span class="caption">{{ 'products.product.volume_pricing.note' | t }}</span>
</div>
{%- endif -%}
</div>
</div>
{%- if show_quick_add -%}
<div class="quick-add no-js-hidden">
{%- liquid
assign product_form_id = 'quick-add-' | append: section_id | append: card_product.id
assign qty_rules = false
if card_product.selected_or_first_available_variant.quantity_rule.min > 1 or card_product.selected_or_first_available_variant.quantity_rule.max != null or card_product.selected_or_first_available_variant.quantity_rule.increment > 1
assign qty_rules = true
endif
-%}
{%- if card_product.variants.size > 1 or qty_rules -%}
<modal-opener data-modal="#QuickAdd-{{ card_product.id }}">
<button
id="{{ product_form_id }}-submit"
type="submit"
name="add"
class="quick-add__submit button button--full-width button--secondary{% if horizontal_quick_add %} card--horizontal__quick-add animate-arrow{% endif %}"
aria-haspopup="dialog"
aria-labelledby="{{ product_form_id }}-submit title-{{ section_id }}-{{ card_product.id }}"
data-product-url="{{ card_product.url }}"
>
{{ 'products.product.choose_options' | t }}
{%- if horizontal_quick_add -%}
<span class="icon-wrap">{% render 'icon-arrow' %}</span>
{%- endif -%}
<div class="loading-overlay__spinner hidden">
<svg
aria-hidden="true"
focusable="false"
class="spinner"
viewBox="0 0 66 66"
xmlns="http://www.w3.org/2000/svg"
>
<circle class="path" fill="none" stroke-width="6" cx="33" cy="33" r="30"></circle>
</svg>
</div>
</button>
</modal-opener>
<quick-add-modal id="QuickAdd-{{ card_product.id }}" class="quick-add-modal">
<div
role="dialog"
aria-label="{{ 'products.product.choose_product_options' | t: product_name: card_product.title | escape }}"
aria-modal="true"
class="quick-add-modal__content global-settings-popup"
tabindex="-1"
>
<button
id="ModalClose-{{ card_product.id }}"
type="button"
class="quick-add-modal__toggle"
aria-label="{{ 'accessibility.close' | t }}"
>
{% render 'icon-close' %}
</button>
<div id="QuickAddInfo-{{ card_product.id }}" class="quick-add-modal__content-info"></div>
</div>
</quick-add-modal>
{%- else -%}
<product-form data-section-id="{{ section.id }}">
{%- form 'product',
card_product,
id: product_form_id,
class: 'form',
novalidate: 'novalidate',
data-type: 'add-to-cart-form'
-%}
<input
type="hidden"
name="id"
value="{{ card_product.selected_or_first_available_variant.id }}"
class="product-variant-id"
disabled
>
<button
id="{{ product_form_id }}-submit"
type="submit"
name="add"
class="quick-add__submit button button--full-width button--secondary{% if horizontal_quick_add %} card--horizontal__quick-add{% endif %}"
aria-haspopup="dialog"
aria-labelledby="{{ product_form_id }}-submit title-{{ section_id }}-{{ card_product.id }}"
aria-live="polite"
data-sold-out-message="true"
{% if card_product.selected_or_first_available_variant.available == false %}
disabled
{% endif %}
>
<span>
{%- if card_product.selected_or_first_available_variant.available -%}
{{ 'products.product.add_to_cart' | t }}
{%- else -%}
{{ 'products.product.sold_out' | t }}
{%- endif -%}
</span>
<span class="sold-out-message hidden">
{{ 'products.product.sold_out' | t }}
</span>
{%- if horizontal_quick_add -%}
<span class="icon-wrap">{% render 'icon-plus' %}</span>
{%- endif -%}
<div class="loading-overlay__spinner hidden">
<svg
aria-hidden="true"
focusable="false"
class="spinner"
viewBox="0 0 66 66"
xmlns="http://www.w3.org/2000/svg"
>
<circle class="path" fill="none" stroke-width="6" cx="33" cy="33" r="30"></circle>
</svg>
</div>
</button>
{%- endform -%}
</product-form>
{%- endif -%}
</div>
{%- endif -%}
<div class="card__badge {{ settings.badge_position }}">
{%- if card_product.available == false -%}
<span
id="Badge-{{ section_id }}-{{ card_product.id }}"
class="badge badge--bottom-left color-{{ settings.sold_out_badge_color_scheme }}"
>
{{- 'products.product.sold_out' | t -}}
</span>
{%- elsif card_product.compare_at_price > card_product.price and card_product.available -%}
<span
id="Badge-{{ section_id }}-{{ card_product.id }}"
class="badge badge--bottom-left color-{{ settings.sale_badge_color_scheme }}"
>
{{- 'products.product.on_sale' | t -}}
</span>
{%- endif -%}
</div>
</div>
</div>
</div>
</div>
{%- else -%}
<div class="card-wrapper product-card-wrapper underline-links-hover">
<div
class="
card card--{{ settings.card_style }}
{% if extend_height %} card--extend-height{% endif %}
{% if settings.card_style == 'card' %} color-{{ settings.card_color_scheme }} gradient{% endif %}
"
style="--ratio-percent: 100%;"
>
<div
class="card__inner{% if settings.card_style == 'standard' %} color-{{ settings.card_color_scheme }} gradient{% endif %} ratio"
style="--ratio-percent: 100%;"
>
<div class="card__media" id="ProductCardPlaceholderImage-{{ card_product.id }}"> {% comment %} Code changed here {% endcomment %}
<div class="media media--transparent">
{%- if placeholder_image -%}
{{ placeholder_image | placeholder_svg_tag: 'placeholder-svg' }}
{%- else -%}
{{ 'product-apparel-2' | placeholder_svg_tag: 'placeholder-svg' }}
{% endif %}
</div>
</div>
</div>
<div class="card__content">
<div class="card__information">
<h3 class="card__heading card__heading--placeholder{% if settings.card_style == 'standard' %} h5{% endif %}">
<a role="link" aria-disabled="true" class="full-unstyled-link">
{{ 'onboarding.product_title' | t }}
</a>
</h3>
<div class="card-information">
{%- if show_vendor -%}
<span class="visually-hidden">{{ 'accessibility.vendor' | t }}</span>
<div class="caption-with-letter-spacing light">{{ 'products.product.vendor' | t }}</div>
{%- endif -%}
{% render 'price', show_compare_at_price: true %}
</div>
</div>
</div>
</div>
</div>
{%- endif -%}
<style>
.card__information {
padding-top: 10px;
}
.collection-product-card__variants {
display: flex;
flex-wrap: wrap;
}
</style>
Create liquid snippet card-product-custom.liquid (v13)
{% comment %}
Renders a product card
Accepts:
- card_product: {Object} Product Liquid object (optional)
- media_aspect_ratio: {String} Size of the product image card. Values are "square" and "portrait". Default is "square" (optional)
- image_shape: {String} Image mask to apply to the product image card. Values are "arch", "blob", "chevronleft", "chevronright", "diamond", "parallelogram", and "round". (optional)
- show_secondary_image: {Boolean} Show the secondary image on hover. Default: false (optional)
- show_vendor: {Boolean} Show the product vendor. Default: false
- show_rating: {Boolean} Show the product rating. Default: false
- extend_height: {Boolean} Card height extends to available container space. Default: true (optional)
- lazy_load: {Boolean} Image should be lazy loaded. Default: true (optional)
- show_quick_add: {Boolean} Show the quick add button.
- section_id: {String} The ID of the section that contains this card.
- horizontal_class: {Boolean} Add a card--horizontal class if set to true. Default: false (optional)
- horizontal_quick_add: {Boolean} Changes the quick add button styles when set to true. Default: false (optional)
- placeholder_image: {String} The placeholder image to use when no product exists. Default: 'product-apparel-2' (optional)
Usage:
{% render 'card-product', show_vendor: section.settings.show_vendor %}
{% endcomment %}
{{ 'component-rating.css' | asset_url | stylesheet_tag }}
{{ 'component-volume-pricing.css' | asset_url | stylesheet_tag }}
{% assign variant_option_name = "Color" %} {% comment %}Setting: Variant Option Name To Display On Collection Page{% endcomment %}
{% assign entry_title = "variant-swatch-mapping" %} {% comment %}Setting: Name of Metaobject entry{% endcomment %}
{% assign lazy_load_all_variants = false %} {% comment %}Setting: Turn Lazy Load on or off. true: ON, false: OFF{% endcomment %}
{%- if card_product and card_product != empty -%}
{%- liquid
assign ratio = 1
if card_product.featured_media and media_aspect_ratio == 'portrait'
assign ratio = 0.8
elsif card_product.featured_media and media_aspect_ratio == 'adapt'
assign ratio = card_product.featured_media.aspect_ratio
endif
if ratio == 0 or ratio == null
assign ratio = 1
endif
-%}
{% assign target_entry = nil %}
{% for entry in shop.metaobjects.variant_swatch_map.values %}
{% if entry.title == entry_title %}
{% assign target_entry = entry %}
{% break %}
{% endif %}
{% endfor %}
{% if target_entry %}
{% assign variant_images_data = target_entry.variant_images_json %}
{% else %}
{% assign variant_images_data = nil %}
{% endif %}
<div class="card-product-custom-div" data-section-id="{{ section.id }}" data-lazy-load-all-variants="{{ lazy_load_all_variants }}">
<div class="card-wrapper product-card-wrapper underline-links-hover">
<div
class="
card card--{{ settings.card_style }}
{% if card_product.featured_media %} card--media{% else %} card--text{% endif %}
{% if settings.card_style == 'card' %} color-{{ settings.card_color_scheme }} gradient{% endif %}
{% if image_shape and image_shape != 'default' %} card--shape{% endif %}
{% if extend_height %} card--extend-height{% endif %}
{% if card_product.featured_media == nil and settings.card_style == 'card' %} ratio{% endif %}
{% if horizontal_class %} card--horizontal{% endif %}
"
style="--ratio-percent: {{ 1 | divided_by: ratio | times: 100 }}%;"
>
<div
class="card__inner {% if settings.card_style == 'standard' %}color-{{ settings.card_color_scheme }} gradient{% endif %}{% if card_product.featured_media or settings.card_style == 'standard' %} ratio{% endif %}"
style="--ratio-percent: {{ 1 | divided_by: ratio | times: 100 }}%;"
>
{%- if card_product.featured_media -%}
<div class="card__media{% if image_shape and image_shape != 'default' %} shape--{{ image_shape }} color-{{ settings.card_color_scheme }} gradient{% endif %}" id="ProductCardImage-{{ card_product.id }}"> {% comment %} Code changed here {% endcomment %}
<div class="media media--transparent media--hover-effect">
{% comment %}theme-check-disable ImgLazyLoading{% endcomment %}
<img
srcset="
{%- if card_product.featured_media.width >= 165 -%}{{ card_product.featured_media | image_url: width: 165 }} 165w,{%- endif -%}
{%- if card_product.featured_media.width >= 360 -%}{{ card_product.featured_media | image_url: width: 360 }} 360w,{%- endif -%}
{%- if card_product.featured_media.width >= 533 -%}{{ card_product.featured_media | image_url: width: 533 }} 533w,{%- endif -%}
{%- if card_product.featured_media.width >= 720 -%}{{ card_product.featured_media | image_url: width: 720 }} 720w,{%- endif -%}
{%- if card_product.featured_media.width >= 940 -%}{{ card_product.featured_media | image_url: width: 940 }} 940w,{%- endif -%}
{%- if card_product.featured_media.width >= 1066 -%}{{ card_product.featured_media | image_url: width: 1066 }} 1066w,{%- endif -%}
{{ card_product.featured_media | image_url }} {{ card_product.featured_media.width }}w
"
src="{{ card_product.featured_media | image_url: width: 533 }}"
sizes="(min-width: {{ settings.page_width }}px) {{ settings.page_width | minus: 130 | divided_by: 4 }}px, (min-width: 990px) calc((100vw - 130px) / 4), (min-width: 750px) calc((100vw - 120px) / 3), calc((100vw - 35px) / 2)"
alt="{{ card_product.featured_media.alt | escape }}"
class="motion-reduce"
{% unless lazy_load == false %}
loading="lazy"
{% endunless %}
width="{{ card_product.featured_media.width }}"
height="{{ card_product.featured_media.height }}"
>
{% comment %}theme-check-enable ImgLazyLoading{% endcomment %}
{%- if card_product.media[1] != null and show_secondary_image -%}
<img
srcset="
{%- if card_product.media[1].width >= 165 -%}{{ card_product.media[1] | image_url: width: 165 }} 165w,{%- endif -%}
{%- if card_product.media[1].width >= 360 -%}{{ card_product.media[1] | image_url: width: 360 }} 360w,{%- endif -%}
{%- if card_product.media[1].width >= 533 -%}{{ card_product.media[1] | image_url: width: 533 }} 533w,{%- endif -%}
{%- if card_product.media[1].width >= 720 -%}{{ card_product.media[1] | image_url: width: 720 }} 720w,{%- endif -%}
{%- if card_product.media[1].width >= 940 -%}{{ card_product.media[1] | image_url: width: 940 }} 940w,{%- endif -%}
{%- if card_product.media[1].width >= 1066 -%}{{ card_product.media[1] | image_url: width: 1066 }} 1066w,{%- endif -%}
{{ card_product.media[1] | image_url }} {{ card_product.media[1].width }}w
"
src="{{ card_product.media[1] | image_url: width: 533 }}"
sizes="(min-width: {{ settings.page_width }}px) {{ settings.page_width | minus: 130 | divided_by: 4 }}px, (min-width: 990px) calc((100vw - 130px) / 4), (min-width: 750px) calc((100vw - 120px) / 3), calc((100vw - 35px) / 2)"
alt=""
class="motion-reduce"
loading="lazy"
width="{{ card_product.media[1].width }}"
height="{{ card_product.media[1].height }}"
>
{%- endif -%}
</div>
</div>
{%- endif -%}
<div class="card__content">
<div class="card__information">
<h3
class="card__heading"
{% if card_product.featured_media == null and settings.card_style == 'standard' %}
id="title-{{ section_id }}-{{ card_product.id }}"
{% endif %}
>
<a
href="{{ card_product.url }}"
id="StandardCardNoMediaLink-{{ section_id }}-{{ card_product.id }}"
class="full-unstyled-link"
aria-labelledby="StandardCardNoMediaLink-{{ section_id }}-{{ card_product.id }} NoMediaStandardBadge-{{ section_id }}-{{ card_product.id }}"
>
{{ card_product.title | escape }}
</a>
</h3>
</div>
<div class="card__badge {{ settings.badge_position }}">
{%- if card_product.available == false -%}
<span
id="NoMediaStandardBadge-{{ section_id }}-{{ card_product.id }}"
class="badge badge--bottom-left color-{{ settings.sold_out_badge_color_scheme }}"
>
{{- 'products.product.sold_out' | t -}}
</span>
{%- elsif card_product.compare_at_price > card_product.price and card_product.available -%}
{%- assign difference = card_product.compare_at_price | minus: card_product.price -%}
{%- assign float_difference = difference | times: 1.0 -%}
{%- assign discount_fraction = float_difference | divided_by: card_product.compare_at_price -%}
{%- assign discount_percentage = discount_fraction | times: 100 | round -%}
<span
id="NoMediaStandardBadge-{{ section_id }}-{{ card_product.id }}"
class="badge badge--bottom-left color-{{ settings.sale_badge_color_scheme }}"
>
{% comment %}{{- 'products.product.on_sale' | t -}}{% endcomment %}
{{- discount_percentage }}% OFF
</span>
{%- endif -%}
</div>
</div>
</div>
<h3
class="card__heading{% if card_product.featured_media or settings.card_style == 'standard' %} h5{% endif %}"
{% if card_product.featured_media or settings.card_style == 'card' %}
id="title-{{ section_id }}-{{ card_product.id }}"
{% endif %}
>
<a
href="{{ card_product.url }}"
id="CardLink-{{ section_id }}-{{ card_product.id }}"
class="full-unstyled-link"
aria-labelledby="CardLink-{{ section_id }}-{{ card_product.id }} Badge-{{ section_id }}-{{ card_product.id }}"
>
</a>
</h3>
</div>
</div>
<div class = "collection-product-card__variants">
{%- for card_product_option in card_product.options_with_values -%}
{% if card_product_option.name == variant_option_name %}
{% assign variant_options_images = variant_images_data.value | where: "variant_name", card_product_option.name %}
{% assign special_handling = false %}
{% if variant_options_images.size > 0 %}
{% assign special_handling = true %}
{% endif %}
{% render 'card-variant-swatch-custom', product: card_product, option: card_product_option, variant_images_data: variant_options_images, lazy_load_all_variants: lazy_load_all_variants %}
{% endif %}
{%- endfor -%}
</div>
<div class="card-wrapper product-card-wrapper underline-links-hover">
<div
class="
card card--{{ settings.card_style }}
{% if card_product.featured_media %} card--media{% else %} card--text{% endif %}
{% if settings.card_style == 'card' %} color-{{ settings.card_color_scheme }} gradient{% endif %}
{% if image_shape and image_shape != 'default' %} card--shape{% endif %}
{% if extend_height %} card--extend-height{% endif %}
{% if card_product.featured_media == nil and settings.card_style == 'card' %} ratio{% endif %}
{% if horizontal_class %} card--horizontal{% endif %}
"
style="--ratio-percent: {{ 1 | divided_by: ratio | times: 100 }}%;"
>
<div class="card__content">
<div class="card__information">
<h3
class="card__heading{% if card_product.featured_media or settings.card_style == 'standard' %} h5{% endif %}"
{% if card_product.featured_media or settings.card_style == 'card' %}
id="title-{{ section_id }}-{{ card_product.id }}"
{% endif %}
>
<a
href="{{ card_product.url }}"
id="CardLink-{{ section_id }}-{{ card_product.id }}"
class="full-unstyled-link"
aria-labelledby="CardLink-{{ section_id }}-{{ card_product.id }} Badge-{{ section_id }}-{{ card_product.id }}"
>
{{ card_product.title | escape }}
</a>
</h3>
<div class="card-information">
{%- if show_vendor -%}
<span class="visually-hidden">{{ 'accessibility.vendor' | t }}</span>
<div class="caption-with-letter-spacing light">{{ card_product.vendor }}</div>
{%- endif -%}
<span class="caption-large light">{{ block.settings.description | escape }}</span>
{%- if show_rating and card_product.metafields.reviews.rating.value != blank -%}
{% liquid
assign rating_decimal = 0
assign decimal = card_product.metafields.reviews.rating.value.rating | modulo: 1
if decimal >= 0.3 and decimal <= 0.7
assign rating_decimal = 0.5
elsif decimal > 0.7
assign rating_decimal = 1
endif
%}
<div
class="rating"
role="img"
aria-label="{{ 'accessibility.star_reviews_info' | t: rating_value: card_product.metafields.reviews.rating.value, rating_max: card_product.metafields.reviews.rating.value.scale_max }}"
>
<span
aria-hidden="true"
class="rating-star"
style="--rating: {{ card_product.metafields.reviews.rating.value.rating | floor }}; --rating-max: {{ card_product.metafields.reviews.rating.value.scale_max }}; --rating-decimal: {{ rating_decimal }};"
></span>
</div>
<p class="rating-text caption">
<span aria-hidden="true">
{{- card_product.metafields.reviews.rating.value }} /
{{ card_product.metafields.reviews.rating.value.scale_max -}}
</span>
</p>
<p class="rating-count caption">
<span aria-hidden="true">({{ card_product.metafields.reviews.rating_count }})</span>
<span class="visually-hidden">
{{- card_product.metafields.reviews.rating_count }}
{{ 'accessibility.total_reviews' | t -}}
</span>
</p>
{%- endif -%}
{% render 'price', product: card_product, price_class: '', show_compare_at_price: true %}
{%- if card_product.quantity_price_breaks_configured? -%}
<div class="card__information-volume-pricing-note">
<span class="caption">{{ 'products.product.volume_pricing.note' | t }}</span>
</div>
{%- endif -%}
</div>
</div>
{%- if show_quick_add -%}
<div class="quick-add no-js-hidden">
{%- liquid
assign product_form_id = 'quick-add-' | append: section_id | append: card_product.id
assign qty_rules = false
if card_product.selected_or_first_available_variant.quantity_rule.min > 1 or card_product.selected_or_first_available_variant.quantity_rule.max != null or card_product.selected_or_first_available_variant.quantity_rule.increment > 1
assign qty_rules = true
endif
-%}
{%- if card_product.variants.size > 1 or qty_rules -%}
<modal-opener data-modal="#QuickAdd-{{ card_product.id }}">
<button
id="{{ product_form_id }}-submit"
type="submit"
name="add"
class="quick-add__submit button button--full-width button--secondary{% if horizontal_quick_add %} card--horizontal__quick-add animate-arrow{% endif %}"
aria-haspopup="dialog"
aria-labelledby="{{ product_form_id }}-submit title-{{ section_id }}-{{ card_product.id }}"
data-product-url="{{ card_product.url }}"
>
{{ 'products.product.choose_options' | t }}
{%- if horizontal_quick_add -%}
<span class="icon-wrap">{% render 'icon-arrow' %}</span>
{%- endif -%}
{%- render 'loading-spinner' -%}
</button>
</modal-opener>
<quick-add-modal id="QuickAdd-{{ card_product.id }}" class="quick-add-modal">
<div
role="dialog"
aria-label="{{ 'products.product.choose_product_options' | t: product_name: card_product.title | escape }}"
aria-modal="true"
class="quick-add-modal__content global-settings-popup"
tabindex="-1"
>
<button
id="ModalClose-{{ card_product.id }}"
type="button"
class="quick-add-modal__toggle"
aria-label="{{ 'accessibility.close' | t }}"
>
{% render 'icon-close' %}
</button>
<div id="QuickAddInfo-{{ card_product.id }}" class="quick-add-modal__content-info"></div>
</div>
</quick-add-modal>
{%- else -%}
<product-form data-section-id="{{ section.id }}">
{%- form 'product',
card_product,
id: product_form_id,
class: 'form',
novalidate: 'novalidate',
data-type: 'add-to-cart-form'
-%}
<input
type="hidden"
name="id"
value="{{ card_product.selected_or_first_available_variant.id }}"
class="product-variant-id"
{% if card_product.selected_or_first_available_variant.available == false %}
disabled
{% endif %}
>
<button
id="{{ product_form_id }}-submit"
type="submit"
name="add"
class="quick-add__submit button button--full-width button--secondary{% if horizontal_quick_add %} card--horizontal__quick-add{% endif %}"
aria-haspopup="dialog"
aria-labelledby="{{ product_form_id }}-submit title-{{ section_id }}-{{ card_product.id }}"
aria-live="polite"
data-sold-out-message="true"
{% if card_product.selected_or_first_available_variant.available == false %}
disabled
{% endif %}
>
<span>
{%- if card_product.selected_or_first_available_variant.available -%}
{{ 'products.product.add_to_cart' | t }}
{%- else -%}
{{ 'products.product.sold_out' | t }}
{%- endif -%}
</span>
<span class="sold-out-message hidden">
{{ 'products.product.sold_out' | t }}
</span>
{%- if horizontal_quick_add -%}
<span class="icon-wrap">{% render 'icon-plus' %}</span>
{%- endif -%}
{%- render 'loading-spinner' -%}
</button>
{%- endform -%}
</product-form>
{%- endif -%}
</div>
{%- endif -%}
<div class="card__badge {{ settings.badge_position }}">
{%- if card_product.available == false -%}
<span
id="Badge-{{ section_id }}-{{ card_product.id }}"
class="badge badge--bottom-left color-{{ settings.sold_out_badge_color_scheme }}"
>
{{- 'products.product.sold_out' | t -}}
</span>
{%- elsif card_product.compare_at_price > card_product.price and card_product.available -%}
<span
id="Badge-{{ section_id }}-{{ card_product.id }}"
class="badge badge--bottom-left color-{{ settings.sale_badge_color_scheme }}"
>
{{- 'products.product.on_sale' | t -}}
</span>
{%- endif -%}
</div>
</div>
</div>
</div>
</div>
{%- else -%}
{%- liquid
assign ratio = 1
if media_aspect_ratio == 'portrait'
assign ratio = 0.8
endif
-%}
<div class="card-wrapper product-card-wrapper underline-links-hover">
<div
class="
card card--{{ settings.card_style }}
{% if extend_height %} card--extend-height{% endif %}
{% if image_shape and image_shape != 'default' %} card--shape{% endif %}
{% if settings.card_style == 'card' %} color-{{ settings.card_color_scheme }} gradient{% endif %}
"
style="--ratio-percent: {{ 1 | divided_by: ratio | times: 100 }}%;"
>
<div
class="card__inner{% if settings.card_style == 'standard' %} color-{{ settings.card_color_scheme }} gradient{% endif %} ratio"
>
<div class="card__media" {% if image_shape and image_shape != 'default' %} shape--{{ image_shape }} color-{{ settings.card_color_scheme }} gradient{% endif %} id="ProductCardPlaceholderImage-{{ card_product.id }}"> {% comment %} Code changed here {% endcomment %}
<div
class="media media--transparent"
>
{%- if placeholder_image -%}
{{ placeholder_image | placeholder_svg_tag: 'placeholder-svg' }}
{%- else -%}
{{ 'product-apparel-2' | placeholder_svg_tag: 'placeholder-svg' }}
{% endif %}
</div>
</div>
</div>
<div class="card__content">
<div class="card__information">
<h3 class="card__heading card__heading--placeholder{% if settings.card_style == 'standard' %} h5{% endif %}">
<a role="link" aria-disabled="true" class="full-unstyled-link">
{{ 'onboarding.product_title' | t }}
</a>
</h3>
<div class="card-information">
{%- if show_vendor -%}
<span class="visually-hidden">{{ 'accessibility.vendor' | t }}</span>
<div class="caption-with-letter-spacing light">{{ 'products.product.vendor' | t }}</div>
{%- endif -%}
{% render 'price', show_compare_at_price: true %}
</div>
</div>
</div>
</div>
</div>
{%- endif -%}
<style>
.card__information {
padding-top: 10px;
}
.collection-product-card__variants {
display: flex;
flex-wrap: wrap;
}
</style>
Create asset card-product-variant-selection-custom.js
document.addEventListener('DOMContentLoaded', function() {
var productGrids = document.querySelectorAll('.grid.product-grid');
productGrids.forEach(function(productGrid) {
var sectionId = productGrid.getAttribute('data-section-id');
var variantDataMap = window['variantDataMap' + sectionId.replace(/-/g, '_')];
productGrid.addEventListener('change', function(e) {
if (e.target.matches('input[type="radio"][data-section-id="' + sectionId + '"]')) {
var card = e.target.closest(`.card-product-custom-div[data-section-id="${sectionId}"]`);
var variantId = e.target.getAttribute('data-variant-id');
var variantData = variantDataMap[variantId];
if (!variantData) {
console.log('No data found for variant:', variantId);
return;
}
// Update the product image with lazy loading logic
var productImageElement = card.querySelector('.card__media img');
if (productImageElement) {
var lazyLoadAllVariants = card.getAttribute('data-lazy-load-all-variants') === 'true';
if (lazyLoadAllVariants) {
var preloadedImage = card.querySelector(`img[data-variant-id="${variantId}"]`);
if (preloadedImage) {
productImageElement.src = preloadedImage.src;
productImageElement.srcset = preloadedImage.srcset;
} else {
console.log('Matching preloaded image not found for variant:', variantId);
}
} else {
// Update src and srcset for responsive images
var dynamicSrcset = [
variantData.imageUrl + '?width=165 165w',
variantData.imageUrl + '?width=360 360w',
variantData.imageUrl + '?width=533 533w',
variantData.imageUrl + '?width=720 720w',
variantData.imageUrl + '?width=940 940w',
variantData.imageUrl + '?width=1066 1066w'
].join(', ');
productImageElement.srcset = dynamicSrcset;
productImageElement.src = variantData.imageUrl;
}
} else {
console.log('No product image element found in this card.');
}
// Update the product URL
var productLinks = card.querySelectorAll('a[id^="CardLink-"], a[id^="StandardCardNoMediaLink-"]');
productLinks.forEach(function(link) {
link.href = variantData.productUrl;
});
} else {
console.log('Change detected, but not on a swatch.');
}
});
});
});
3. Edit Collection Page To Render Variant Swatch Snippet
Edit main-collection-product-grid.liquid
Add data-section-id="{{ section.id }}"
to the element with class grid product-grid
that contains the card-product-custom.liquid render
<ul
id="product-grid"
data-id="{{ section.id }}"
data-section-id="{{ section.id }}"
class="
grid product-grid grid--{{ section.settings.columns_mobile }}-col-tablet-down
grid--{{ section.settings.columns_desktop }}-col-desktop
"
>
Replace original card-product with new card-product-custom version:
{% render 'card-product-custom',
card_product: product,
media_aspect_ratio: section.settings.image_ratio,
image_shape: section.settings.image_shape,
show_secondary_image: section.settings.show_secondary_image,
show_vendor: section.settings.show_vendor,
show_rating: section.settings.show_rating,
lazy_load: lazy_load,
show_quick_add: section.settings.enable_quick_add,
section_id: section.id
%}
Add scripting to bottom of the file
<script>
var variantDataMap{{ section.id | replace: '-', '_' }} = {
{% assign isFirstVariant = true %}
{% for product in collection.products %}
{% for variant in product.variants %}
{% if variant.featured_media %}
{% unless isFirstVariant %},{% endunless %}
"{{ variant.id }}": {
"imageUrl": "{{ variant.featured_media | img_url: 'master' }}",
"productUrl": "{{ product.url | append: '?variant=' | append: variant.id }}"
}
{% assign isFirstVariant = false %}
{% endif %}
{% endfor %}
{% endfor %}
};
</script>
{{ 'card-product-variant-selection-custom.js' | asset_url | script_tag }}
4. Upload Image Files and Create Metaobject and Metaobject Entry
Upload your Swatch image files
Left Menu Bar: Content —> Files
Create Metaobject “Variant Swatch Map”
Make sure handles of the metaobject and fields match those shown in the video since the code references those exact handles.
- Field 1: Title
- Field 2: Variant Images JSON
- The JSON will have the following schema defined:
{ "$id": "variants_images.schema.json", "$schema": "<http://json-schema.org/draft-07/schema#>", "title": "Variants List", "description": "A list of variant swatches", "type": "array", "items": { "type": "object", "properties": { "variant_name": { "type": "string", "description": "The variant option name." }, "variant_value": { "type": "string", "description": "The variant option value." }, "variant_swatch": { "type": "string", "description": "The filename or URL of the image representing the color." } }, "required": [ "variant_name", "variant_value", "variant_swatch" ] } }
Create Metaobject entry called “variant-swatch-mapping”
Add JSON entries that reference your uploaded images files. For example:
[
{
"variant_name": "Material",
"variant_value": "Cotton",
"variant_swatch": "cotton.jpg"
},
{
"variant_name": "Material",
"variant_value": "Polyester",
"variant_swatch": "polyester.jpg"
},
{
"variant_name": "Color",
"variant_value": "Blue",
"variant_swatch": "bluerose.jpg"
},
{
"variant_name": "Color",
"variant_value": "Green",
"variant_swatch": "greenimage.jpg"
}
]
(Optional) Add Swatches To Featured Collections and Search
Turn off “Show second image on hover” from theme editor
Featured Collection Page: update featured-collection.liquid code
Add data-section-id="{{ section.id }}"
to the element with class grid product-grid
that contains the card-product-custom.liquid render
<ul
id="Slider-{{ section.id }}"
data-section-id="{{ section.id }}"
class="grid product-grid contains-card contains-card--product{% if settings.card_style == 'standard' %} contains-card--standard{% endif %} grid--{{ section.settings.columns_desktop }}-col-desktop{% if section.settings.collection == blank %} grid--2-col-tablet-down{% else %} grid--{{ section.settings.columns_mobile }}-col-tablet-down{% endif %}{% if show_mobile_slider or show_desktop_slider %} slider{% if show_desktop_slider %} slider--desktop{% endif %}{% if show_mobile_slider %} slider--tablet grid--peek{% endif %}{% endif %}"
role="list"
aria-label="{{ 'general.slider.name' | t }}"
>
similar to main-collection-product-grid.liquid, update featured-collection.liquid to call the new version of card-product-custom (2 spots in the code)
{% render 'card-product-custom',
card_product: product,
media_aspect_ratio: section.settings.image_ratio,
image_shape: section.settings.image_shape,
show_secondary_image: section.settings.show_secondary_image,
show_vendor: section.settings.show_vendor,
show_rating: section.settings.show_rating,
show_quick_add: section.settings.enable_quick_add,
section_id: section.id
%}
{% render 'card-product-custom',
show_vendor: section.settings.show_vendor,
placeholder_image: placeholder_image
image_shape: section.settings.image_shape,
placeholder_image: placeholder_image
section_id: section.id
%}
add scripting at end of file:
<script>
var variantDataMap{{ section.id | replace: '-', '_' }} = {
{% assign isFirstVariant = true %}
{% for product in section.settings.collection.products %}
{% for variant in product.variants %}
{% if variant.featured_media %}
{% unless isFirstVariant %},{% endunless %}
"{{ variant.id }}": {
"imageUrl": "{{ variant.featured_media | img_url: 'master' }}",
"productUrl": "{{ product.url | append: '?variant=' | append: variant.id }}"
}
{% assign isFirstVariant = false %}
{% endif %}
{% endfor %}
{% endfor %}
};
</script>
{{ 'card-product-variant-selection-custom.js' | asset_url | script_tag }}
Search Page: update main-search.liquid code
Add data-section-id="{{ section.id }}"
to the element with class grid product-grid
that contains the card-product-custom.liquid render
<ul class="grid product-grid grid--{{ section.settings.columns_mobile }}-col-tablet-down grid--{{ section.settings.columns_desktop }}-col-desktop"
role="list"
data-section-id="{{ section.id }}"
>
similar to main-collection-product-grid.liquid, update main-search.liquid to call the new version of card-product-custom
{% render 'card-product-custom',
card_product: item,
media_aspect_ratio: section.settings.image_ratio,
image_shape: section.settings.image_shape,
show_secondary_image: section.settings.show_secondary_image,
show_vendor: section.settings.show_vendor,
show_rating: section.settings.show_rating,
lazy_load: lazy_load,
section_id: section.id
%}
add scripting at end of file:
<script>
var variantDataMap{{ section.id | replace: '-', '_' }} = {
{% assign isFirstVariant = true %}
{% for item in search.results %}
{% if item.object_type == 'product' %}
{% for variant in item.variants %}
{% if variant.featured_media %}
{% unless isFirstVariant %},{% endunless %}
"{{ variant.id }}": {
"imageUrl": "{{ variant.featured_media | img_url: 'master' }}",
"productUrl": "{% if item.url contains '?' %}{{ item.url | append: '&variant=' | append: variant.id }}{% else %}{{ item.url | append: '?variant=' | append: variant.id }}{% endif %}"
}
{% assign isFirstVariant = false %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
};
</script>
{{ 'card-product-variant-selection-custom.js' | asset_url | script_tag }}