Skip to content
Product Variant Swatches for 2024 (v3) - Free Tutorial
Browse other ways to boost conversion rate & profit

Product Variant Swatches for 2024 (v3) - Free Tutorial

Want personalized guidance adding this to your store?

Check out our Insiders community: https://www.skool.com/the-prompted

Members of The Prompted community receive a detailed store audit, 1-on-1 guidance for implementing new features, and access to an exclusive theme. You'll also get marketing support, the same tactics we use to spend over $100k/mo on Meta Ads.

---

In this tutorial, we’re looking at product variant swatches again. 

While we’re still waiting for the native swatches to finally be available, we have to use some workarounds like what I’ll be showing you in this customization. And whether or not you installed our previous version of our swatch customization, I'll show you step by step how to add the swatches to your store, without needing to pay for an app or hire a developer.

Compatible Themes: This code should work on all free v14 Shopify themes (Dawn, Refresh, Craft, Studio, Publisher, Crave, Origin, Taste, Colorblock, Sense, Ride, Spotlight). For v15 themes, you will need to use this tutorial instead.

Links:

 

Upload Image Files

Upload your Swatch image files

Left Menu Bar: Content —> Files

Create Metaobjects and Metafields

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.

  1. Field 1: Title
    1. Regular Expression: ^[a-zA-Z0-9_-]+$ (pattern to match - alphanumeric with - and _)
  2. Field 2: Variant Images JSON
    1. 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."
          },
          "variant_hex": {
            "type": "string",
            "description": "The HEX value or description of the color."
          }
        },
        "required": [
          "variant_name",
          "variant_value"
        ]
      }
    }
    

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"
  },
  {
    "variant_name": "Color",
    "variant_value": "Black",
    "variant_swatch": "",
    "variant_hex": "#000000"
  },
  {
    "variant_name": "Color",
    "variant_value": "Red",
    "variant_swatch": "",
    "variant_hex": "#FF0000"
  }
]

Create metafield Variant Swatch Map Override

Use type Variant Swatch Map.

This metafield is for any products you wish to have product-specific swatch mapping. It will override the default metaobject entry variant-swatch-mapping

Edit Theme Code

Edit main-product.liquid schema

Found under the variant picker block

        {
          "type": "header",
          "content": "Custom Swatch"
        },
        {
          "id": "swatch_shape_custom",
          "label": "Swatch (custom)",
          "type": "select",
          "info": "Variant picker style must be Pills to use the custom swatches", 
          "options": [
            {
              "value": "circle",
              "label": "t:sections.main-product.blocks.variant_picker.settings.swatch_shape.options__1.label"
            },
            {
              "value": "square",
              "label": "t:sections.main-product.blocks.variant_picker.settings.swatch_shape.options__2.label"
            },
            {
              "value": "none",
              "label": "t:sections.main-product.blocks.variant_picker.settings.swatch_shape.options__3.label"
            }
          ],
          "default": "none"
        },
        {
          "type": "text",
          "id": "variant_swatch_metaobject",
          "label": "Default Variant Swatch Map Metaobject",
          "default": "variant-swatch-mapping",
          "info": "Can be overridden with product metafield"
        },
        {
          "type": "range",
          "id": "swatch_size",
          "min": 30,
          "max": 60,
          "step": 1,
          "unit": "px",
          "label": "Swatch Size",
          "default": 40
        }

Edit product-variant-picker.liquid

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 -%}

{% if product.metafields.custom.variant_swatch_map_override.value %}
  {% assign target_entry = product.metafields.custom.variant_swatch_map_override.value %}
{% else %}  
  {% assign entry_title = block.settings.variant_swatch_metaobject %} 
  {% 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 %}
{% endif %}
    
{% 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' -%}

{% if block.settings.swatch_shape_custom == "none" %}

	... existing code here ...

{% else %}
	<style>
	  :root {
	    --swatch-size: {{ block.settings.swatch_size }}px;
	    --swatch-border-radius: {% if block.settings.swatch_shape_custom == 'circle' %}50%{% else %}0%{% endif %};
	  }
	</style>
	{{ 'component-product-variant-swatch-custom.css' | asset_url | stylesheet_tag }}
	  
  {% assign variant_options_images = variant_images_data.value | where: "variant_name", option.name %}
  <fieldset class="js product-form__input product-form__input--pill">
    {% if variant_options_images.size > 0 %}
      <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>
{% endif %}

Add js at end of the file

<script src="{{ 'product-variant-selection-custom.js' | asset_url }}" defer="defer"></script>

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 = '//' | append: shop.permanent_domain | append: '/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: '100x100' %}
            {% 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: '100x100' %}
            {% 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: '100x100' %}
            {% if variant.available %}
              {% assign option_disabled = false %}
            {% endif %}
          {% endif %}
      {% endcase %}
  
      {% if variant_image_url %}
        {% assign swatch_found = false %}
        {% for item in variant_images_data %}
          {% if item.variant_value == value %}
            {% if item.variant_swatch != blank %}
              {% assign variant_image_url = base_store_files_url | append: item.variant_swatch %}
              {% assign swatch_found = true %}
            {% elsif item.variant_hex %}
              {% assign hex_color = item.variant_hex | replace: '#', '%23' %}
              {% assign svg = '<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40"><rect width="100%" height="100%" fill="' | append: hex_color | append: '" /></svg>' %}
              {% assign encoded_svg = svg | replace: '"', '%22' | replace: '<', '%3C' | replace: '>', '%3E' %}
              {% assign variant_image_url = 'data:image/svg+xml;charset=utf-8,' | append: encoded_svg %}
              {% assign swatch_found = true %}
            {% endif %}
            {% if swatch_found %}
              {% break %}
            {% endif %}
          {% 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>

Create asset component-product-variant-swatch-custom.css

.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: var(--swatch-size);
    height: var(--swatch-size);
    border: 1px solid #777 !important;
    border-radius: var(--swatch-border-radius) !important;
    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;
}

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);
        }
    });
});

 

 

Browse other ways to boost conversion rate & profit