In this customization, we're adding product swatches to your shopify store.
Many of you liked the previous color swatch video that we uploaded, and in it you asked for some additional features.
So here's the updated version that’s generalized for any variant, not just colors.
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 Product Page
- Add variant swatches to your Collection Page
- Add variant swatches to your Collection Filters
1. Create New Snippet And Asset Files
Create liquid snippet product-variant-swatch-custom.liquid
{% comment %}
Description:
Renders product variant swatch options based on image URLs. Defaults to buttons if no image URL is present.
This is generalized for any variant type.
Accepts:
- product: {Object} product object.
- option: {Object} current product_option object.
- variant_images_data: list of variant images
Usage:
{% render '**product-variant-swatch-custom**',
product: product,
option: option,
variant_images_data: variant_images_data
%}
{% endcomment %}
{% assign base_store_files_url = '//YOUR-SHOP-NAME.myshopify.com/cdn/shop/files/' %}
{%- liquid
assign product_form_id = 'product-form-' | append: section.id
-%}
<div class = "product-form-swatch__variants">
{% for value in option.values %}
{% assign variant_image_url = nil %}
{% assign option_disabled = true %}
{% for variant in product.variants %}
{% case option.position %}
{% when 1 %}
{% if variant.option1 == value %}
{% assign variant_image_url = variant.featured_media | img_url: '300x300' %}
{% if variant.available %}
{% assign option_disabled = false %}
{% endif %}
{% 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' %}
{% if variant.available %}
{% assign option_disabled = false %}
{% endif %}
{% 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' %}
{% if variant.available %}
{% assign option_disabled = false %}
{% endif %}
{% endif %}
{% endcase %}
{% 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="product-form__swatch">
<input
type="radio"
id="{{ section.id }}-{{ option.position }}-{{ forloop.index0 }}"
name="{{ option.name }}"
value="{{ value | escape }}"
form="{{ product_form_id }}"
data-product-id="{{ product.id }}"
data-image-url="{{ variant_image_url }}"
{% if option.selected_value == value %}
checked
{% endif %}
{% if option_disabled %}
class="disabled"
{% endif %}
>
<label for="{{ section.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 %}
</div>
<style>
.product-form-swatch__variants {
display: flex;
flex-wrap: wrap;
}
.product-form__swatch {
display: inline-block;
margin-right: 5px;
}
.product-form__swatch input {
display: none;
}
.product-form__swatch label {
display: block;
width: 40px; /* Adjust for desired swatch size */
height: 40px; /* Adjust for desired swatch size */
border: 1px solid #777 !important;
border-radius: 50% !important; /* 50% for circle 0% for square */
background-size: cover;
cursor: pointer;
transition: border-color 0.3s ease;
padding: 1rem !important;
}
.product-form__swatch label:hover {
border-color: #333 !important;
}
.product-form__swatch input:checked + label {
border-color: #333 !important;
border-width: 2px !important;
box-shadow: inset 0 0 0 1px #fff;
}
.product-form__swatch input.disabled + label {
opacity: 0.5;
}
.product-form__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 asset product-variant-selection-custom.js
document.addEventListener('DOMContentLoaded', function() {
var variantInputs = document.querySelectorAll('.product-form__swatch input[type="radio"], .product-form__swatch button');
function updateVariantDisplay(e) {
console.log('Variant changed'); // Debugging statement
var selectedValue = e.target.tagName === 'BUTTON' ? e.target.getAttribute('data-value') : e.target.value;
var optionName = e.target.name;
var variantDisplay = document.getElementById('selected' + optionName);
if(variantDisplay) {
variantDisplay.textContent = selectedValue;
}
}
variantInputs.forEach(function(input) {
input.addEventListener('change', updateVariantDisplay);
if(input.tagName === 'BUTTON') {
input.addEventListener('click', updateVariantDisplay);
}
});
console.log('DOMContentLoaded event listener set up.'); // Debugging statement
});
2. Edit Product Page To Render Variant Swatch Snippet
Edit product-variant-picker.liquid (v13)
Assign variables and update fieldset around the loop:
{%- for option in product.options_with_values -%}
We will only edit the picker_type “button”. We will not edit the picker_type “swatch” as this hasn’t been fully rolled out yet and we don’t want to modify it.
Code before the loop code {%- for option in product.options_with_values -%}
{% assign entry_title = "variant-swatch-mapping" %} {% comment %}Setting: Name of Metaobject entry{% endcomment %}
{% 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 %}
Add this code below {%- elsif picker_type == 'button' -%}
{% assign variant_options_images = variant_images_data.value | where: "variant_name", option.name %}
{% assign special_handling = false %}
{% if variant_options_images.size > 0 %}
{% assign special_handling = true %}
{% endif %}
<fieldset class="js product-form__input product-form__input--pill {% if special_handling %}product-form__special-input{% endif %}">
{% if special_handling %}
<legend class="form__label">
{{ option.name }}:
<span id="selected{{ option.name }}">{{ option.selected_value }}</span>
</legend>
{% render 'product-variant-swatch-custom', product: product, option: option, variant_images_data: variant_options_images %}
{% else %}
<legend class="form__label">{{ option.name }}</legend>
{% render 'product-variant-options',
product: product,
option: option,
block: block,
picker_type: picker_type
%}
{% endif %}
</fieldset>
Comment out or remove the previous code below {%- elsif picker_type == 'button' -%}
{% comment %}
<fieldset class="js product-form__input product-form__input--pill">
<legend class="form__label">{{ option.name }}</legend>
{% render 'product-variant-options',
product: product,
option: option,
block: block,
picker_type: picker_type
%}
</fieldset>
{% endcomment %}
Add js at end of the file
<script src="{{ 'product-variant-selection-custom.js' | asset_url }}" defer="defer"></script>
Edit product-variant-picker.liquid (v11, v12)
Assign variables and update fieldset around the loop:
{%- for option in product.options_with_values -%}
Note it shows up twice, but we will only edit for the picker_type “button” (at time of writing, this is the first instance of the loop), ie. under this code
Final code surrounding the loop code {%- for option in product.options_with_values -%}
{% assign entry_title = "variant-swatch-mapping" %} {% comment %}Setting: Name of Metaobject entry{% endcomment %}
{% 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 %}
{%- for option in product.options_with_values -%}
{% assign variant_options_images = variant_images_data.value | where: "variant_name", option.name %}
{% assign special_handling = false %}
{% if variant_options_images.size > 0 %}
{% assign special_handling = true %}
{% endif %}
<fieldset class="js product-form__input {% if special_handling %}product-form__special-input{% endif %}">
{% if special_handling %}
<legend class="form__label">
{{ option.name }}:
<span id="selected{{ option.name }}">{{ option.selected_value }}</span>
</legend>
{% render 'product-variant-swatch-custom', product: product, option: option, variant_images_data: variant_options_images %}
{% else %}
<legend class="form__label">{{ option.name }}</legend>
{% render 'product-variant-options', product: product, option: option, block: block %}
{% endif %}
</fieldset>
Add js at end of the file
{{ 'product-variant-selection-custom.js' | asset_url | script_tag }}
3. 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” and 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"
}
]