Skip to content
Native Collection Swatches - Free Tutorial
Browse other ways to boost conversion rate & profit

Native Collection Swatches - Free Tutorial

In this tutorial, we're using Shopify's native swatches to add product variant swatches to the product and collection page. Unlike our previous methods, this approach leverages Shopify’s built-in functionality for a more seamless and optimized experience.

We show you how to use the product page swatches that are available out of the box in Shopify's themes. After that, we show you how to plug into this existing functionality and adding the swatches to the collection page too.

 

Compatible Themes: This code should work on all free Shopify themes (Dawn, Refresh, Craft, Studio, Publisher, Crave, Origin, Taste, Colorblock, Sense, Ride, Spotlight).

Activate native swatches

  • Assign category metafield color to relevant products
  • Connect variant to color metafield

Edit settings_schema.json

Add product card swatch settings


  {
    "name": "Product Card Swatches",
    "settings": [
      {
        "type": "checkbox",
        "label": "Enable Product Card Swatches",
        "id": "card_product_swatch_enable",
        "default": false
      },
      {
        "type": "header",
        "content": "Swatch Format Settings"
      },
      {
        "type": "checkbox",
        "id": "card_product_swatch_show_option_name",
        "label": "Show Option Name",
        "default": false
      },
      {
        "type": "checkbox",
        "id": "card_product_swatch_centered",
        "label": "Center Swatches",
        "default": false
      },
      {
        "id": "card_product_swatch_shape",
        "label": "t:sections.main-product.blocks.variant_picker.settings.swatch_shape.label",
        "type": "select",
        "info": "t:sections.main-product.blocks.variant_picker.settings.swatch_shape.info",
        "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"
          }
        ],
        "default": "circle"
      },
      {
        "type": "range",
        "id": "card_product_swatch_size",
        "label": "Swatch Size",
        "min": 1,
        "max": 3,
        "step": 0.1,
        "default": 2,
        "unit": "rem"
      },
      {
        "type": "range",
        "id": "card_product_swatch_margin_top",
        "label": "Margin Top",
        "min": 0,
        "max": 3,
        "step": 0.1,
        "default": 0.5,
        "unit": "rem"
      },
      {
        "type": "range",
        "id": "card_product_swatch_margin_right",
        "label": "Margin Right",
        "min": 0,
        "max": 3,
        "step": 0.1,
        "default": 0.5,
        "unit": "rem"
      },
      {
        "type": "range",
        "id": "card_product_swatch_margin_bottom",
        "label": "Margin Bottom",
        "min": 0,
        "max": 3,
        "step": 0.1,
        "default": 0.2,
        "unit": "rem"
      },
      {
        "type": "range",
        "id": "card_product_swatch_margin_left",
        "label": "Margin Left",
        "min": 0,
        "max": 3,
        "step": 0.1,
        "default": 0,
        "unit": "rem"
      },
      {
        "type": "range",
        "id": "card_product_information_padding_top",
        "label": "Card Information Padding Top",
        "min": 0,
        "max": 3,
        "step": 0.1,
        "default": 1.3,
        "unit": "rem"
      },
      {
        "type": "range",
        "id": "card_product_information_padding_bottom",
        "label": "Card Information Padding Bottom",
        "min": 0,
        "max": 3,
        "step": 0.1,
        "default": 1.3,
        "unit": "rem"
      },
      {
        "type": "checkbox",
        "id": "card_product_swatch_show_from_price",
        "label": "Show From Price",
        "default": true,
        "info": "This will show the 'from' price in the product card if the product has multiple prices."
      },
      {
        "type": "header",
        "content": "Product Card Settings"
      },
      {
        "type": "select",
        "id": "card_product_swatch_image_ratio",
        "options": [
          {
            "value": "adapt",
            "label": "t:sections.main-collection-product-grid.settings.image_ratio.options__1.label"
          },
          {
            "value": "portrait",
            "label": "t:sections.main-collection-product-grid.settings.image_ratio.options__2.label"
          },
          {
            "value": "square",
            "label": "t:sections.main-collection-product-grid.settings.image_ratio.options__3.label"
          }
        ],
        "default": "adapt",
        "label": "t:sections.main-collection-product-grid.settings.image_ratio.label"
      },
      {
        "type": "select",
        "id": "card_product_swatch_image_shape",
        "options": [
          {
            "value": "default",
            "label": "t:sections.all.image_shape.options__1.label"
          },
          {
            "value": "arch",
            "label": "t:sections.all.image_shape.options__2.label"
          },
          {
            "value": "blob",
            "label": "t:sections.all.image_shape.options__3.label"
          },
          {
            "value": "chevronleft",
            "label": "t:sections.all.image_shape.options__4.label"
          },
          {
            "value": "chevronright",
            "label": "t:sections.all.image_shape.options__5.label"
          },
          {
            "value": "diamond",
            "label": "t:sections.all.image_shape.options__6.label"
          },
          {
            "value": "parallelogram",
            "label": "t:sections.all.image_shape.options__7.label"
          },
          {
            "value": "round",
            "label": "t:sections.all.image_shape.options__8.label"
          }
        ],
        "default": "default",
        "label": "t:sections.all.image_shape.label",
        "info": "t:sections.all.image_shape.info"
      },
      {
        "type": "checkbox",
        "id": "card_product_swatch_show_secondary_image",
        "default": false,
        "label": "t:sections.main-collection-product-grid.settings.show_secondary_image.label"
      },
      {
        "type": "checkbox",
        "id": "card_product_swatch_show_vendor",
        "default": false,
        "label": "t:sections.main-collection-product-grid.settings.show_vendor.label"
      },
      {
        "type": "checkbox",
        "id": "card_product_swatch_show_rating",
        "default": false,
        "label": "t:sections.main-collection-product-grid.settings.show_rating.label",
        "info": "t:sections.main-collection-product-grid.settings.show_rating.info"
      },
      {
        "type": "select",
        "id": "card_product_swatch_quick_add",
        "default": "none",
        "label": "t:sections.main-collection-product-grid.settings.quick_add.label",
        "info": "t:sections.main-collection-product-grid.settings.quick_add.info",
        "options": [
          {
            "value": "none",
            "label": "t:sections.main-collection-product-grid.settings.quick_add.options.option_1"
          },
          {
            "value": "standard",
            "label": "t:sections.main-collection-product-grid.settings.quick_add.options.option_2"
          },
          {
            "value": "bulk",
            "label": "t:sections.main-collection-product-grid.settings.quick_add.options.option_3"
          }
        ]
      }
    ]
  },

Create snippet card-product-swatch.liquid

{% if card_product.options_with_values.size > 0 %}
  {% for option in card_product.options_with_values %}
    {% assign swatch_count = option.values | map: 'swatch' | compact | size %}
    {% if swatch_count > 0 %}
      <fieldset class="js product-card-swatch product-form__input product-form__input--swatch">
        {% if settings.card_product_swatch_show_option_name %}
          <legend class="form__label">{{ option.name }}</legend>
        {% endif %}
        <div style="line-height: 0;">
          {% for value in option.values %}
            {% if value.swatch %}
              {% assign option_position = option.position %}
              {% assign option_disabled = true %}
              {% assign matched_variant_id = blank %}
              
              {% for variant in card_product.variants %}
                {% if option_position == 1 and variant.option1 == value and variant.available %}
                  {% assign matched_variant_id = variant.id %}
                  {% assign option_disabled = false %}
                  {% break %}
                {% elsif option_position == 2 and variant.option2 == value and variant.available %}
                  {% assign matched_variant_id = variant.id %}
                  {% assign option_disabled = false %}
                  {% break %}
                {% elsif option_position == 3 and variant.option3 == value and variant.available %}
                  {% assign matched_variant_id = variant.id %}
                  {% assign option_disabled = false %}
                  {% break %}
                {% endif %}
              {% endfor %}
              
              {% capture input_id %}
                card-swatch-{{ option.name | handleize }}-{{ forloop.index0 }}-{{ card_product.id }}
              {% endcapture %}
              
              <input
                type="radio"
                class="swatch-input__input card-swatch{% if option_disabled %} disabled{% endif %}"
                id="{{ input_id }}"
                name="card-swatch-{{ card_product.id }}-{{ option.name | handleize }}"
                {% if option_disabled %} disabled{% endif %}
                data-product-handle="{{ card_product.handle }}"
                data-variant-id="{{ matched_variant_id }}"
                data-option-position="{{ option_position }}"
                data-swatch-value="{{ value }}"
              >
              
              <label for="{{ input_id }}" class="swatch-input__label">
                {% render 'swatch', swatch: value.swatch, shape: settings.card_product_swatch_shape %}
              </label>
            {% endif %}
          {% endfor %}
        </div>
      </fieldset>
    {% endif %}
  {% endfor %}
{% endif %}

Edit theme.liquid

Load js and css for product card swatches

    {% if settings.card_product_swatch_enable %}
      {{ 'component-product-variant-picker.css' | asset_url | stylesheet_tag }}
      {{ 'component-swatch-input.css' | asset_url | stylesheet_tag }}
      {{ 'component-swatch.css' | asset_url | stylesheet_tag }}
      <script src="{{ 'card-product-fetch.js' | asset_url }}" defer="defer"></script>

      {% style %}
        .product-card-swatch.product-form__input--swatch {
          position: relative;
          z-index: 2;
          {% if settings.card_product_swatch_centered %}justify-content: center;{% endif %}
        }
        .product-card-swatch.product-form__input--swatch .swatch-input__label {
          position: relative;
          z-index: 2;
        }
        .product-card-swatch.product-form__input {
          flex: 0 0 100%;
          padding: 0;
          max-width: 44rem;
          min-width: fit-content;
          border: none;
        }
        .product-card-swatch.product-form__input--swatch .swatch-input__input+.swatch-input__label {
          --swatch-input--size: {{ settings.card_product_swatch_size | default: 2 }}rem;
          margin-top: {{ settings.card_product_swatch_margin_top }}rem;
          margin-right: {{ settings.card_product_swatch_margin_right }}rem;
          margin-bottom: {{ settings.card_product_swatch_margin_bottom }}rem;
          margin-left: {{ settings.card_product_swatch_margin_left }}rem;
        }
        .product-card-swatch .swatch-input__input+.swatch-input__label {
          {% if settings.card_product_swatch_shape == 'circle' %}
            --swatch-input--border-radius: 50%;
          {% else %}
            --swatch-input--border-radius: .2rem;
          {% endif %}
        }
        .card__information.card__information--swatch {
          padding-top: {{ settings.card_product_information_padding_top }}rem;
          padding-bottom: {{ settings.card_product_information_padding_bottom }}rem;
        }
      {% endstyle %}
      
    {% endif %}

Edit price.liquid

Add target definition

  elsif settings.card_product_swatch_enable and card_product_user_selected
    assign target = product.selected_or_first_available_variant

Add from pricing text option

  elsif settings.card_product_swatch_enable and settings.card_product_swatch_show_from_price and card_product_user_selected and product.price_varies
    assign money_price = 'products.product.price.from_price_html' | t: price: money_price

Create section card-product-section.liquid

{% assign card_product = product %}
{% render 'card-product',
  card_product: card_product,
  media_aspect_ratio: settings.card_product_swatch_image_ratio,
  image_shape: settings.card_product_swatch_image_shape,
  show_secondary_image: settings.card_product_swatch_show_secondary_image,
  show_vendor: settings.card_product_swatch_show_vendor,
  show_rating: settings.card_product_swatch_show_rating,
  lazy_load: true,
  skip_styles: false,
  quick_add: settings.card_product_swatch_quick_add,
  section_id: section.id,
  card_product_user_selected: true
%}

{% schema %}
{
  "name": "Card Product Ajax",
  "tag": "section",
  "class": "section",
  "presets": [
    {
      "name": "Card Product Ajax"
    }
  ]
}
{% endschema %}

Create asset card-product-fetch.js

document.addEventListener('DOMContentLoaded', () => {
  document.addEventListener('change', onCardSwatchChange);
});

function onCardSwatchChange(event) {
  if (!event.target.classList.contains('card-swatch')) return;
  const productHandle = event.target.dataset.productHandle;
  if (!productHandle) return;
  const card = event.target.closest('.card-wrapper.product-card-wrapper');
  if (!card) return;
  const variantId = event.target.dataset.variantId;
  if (!variantId) return;
  const sectionId = 'card-product-section';
  const url = `/products/${productHandle}?variant=${variantId}&section_id=${sectionId}`;

  fetch(url)
    .then((res) => res.text())
    .then((responseText) => {
      const parser = new DOMParser();
      const doc = parser.parseFromString(responseText, 'text/html');
      const newCardEl = doc.querySelector(`#card-product-${card.dataset.productId}`);
      if (newCardEl) card.replaceWith(newCardEl);
    })
    .catch(() => {});
}

function buildCardFetchUrl(productHandle, optionValues, sectionId) {
  const base = `/products/${productHandle}`;
  const params = new URLSearchParams();
  params.set('section_id', sectionId);
  if (optionValues.length > 0) {
    params.set('option_values', optionValues.join(','));
  }
  return `${base}?${params.toString()}`;
}

function getCardOptionValues(card) {
  const fieldsets = card.querySelectorAll('.product-form__input--swatch');
  let chosen = [];
  fieldsets.forEach((fieldset) => {
    const checked = fieldset.querySelector('input[type="radio"]:checked');
    if (checked) {
      const pos = parseInt(checked.dataset.optionPosition, 10);
      const val = checked.dataset.swatchValue;
      chosen[pos - 1] = val;
    }
  });
  return chosen.filter((val) => val !== undefined);
}

Edit card-product.liquid

Assign variant_media and card_product_url variables

  {% if settings.card_product_swatch_enable and card_product_user_selected %}
    {% assign variant_media = card_product.selected_or_first_available_variant.featured_media | default: card_product.featured_media %}
    {% assign card_product_url = card_product.url | append: '?variant=' | append: card_product.selected_or_first_available_variant.id %}
  {% else %}
    {% assign variant_media = card_product.featured_media %}
    {% assign card_product_url = card_product.url %}
  {% endif %}

Add data attributes to card-wrapper div

       id="card-product-{{ card_product.id }}"
       data-product-id="{{ card_product.id }}"

Update card_product.featured_media to variant_media in if statement

{%- if variant_media -%}

Update card_product.featured_media to variant_media in <img> element

              <img
                srcset="
                  {%- if variant_media.width >= 165 -%}{{ variant_media | image_url: width: 165 }} 165w,{%- endif -%}
                  {%- if variant_media.width >= 360 -%}{{ variant_media | image_url: width: 360 }} 360w,{%- endif -%}
                  {%- if variant_media.width >= 533 -%}{{ variant_media | image_url: width: 533 }} 533w,{%- endif -%}
                  {%- if variant_media.width >= 720 -%}{{ variant_media | image_url: width: 720 }} 720w,{%- endif -%}
                  {%- if variant_media.width >= 940 -%}{{ variant_media | image_url: width: 940 }} 940w,{%- endif -%}
                  {%- if variant_media.width >= 1066 -%}{{ variant_media | image_url: width: 1066 }} 1066w,{%- endif -%}
                  {{ variant_media | image_url }} {{ variant_media.width }}w
                "
                src="{{ variant_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="{{ variant_media.alt | escape }}"
                class="motion-reduce"
                {% unless lazy_load == false %}
                  loading="lazy"
                {% endunless %}
                width="{{ variant_media.width }}"
                height="{{ variant_media.height }}"
              >

Update card_product.url to card_product_url variable in card__information hyperlink (2 locations)

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

Update card_product.url to card_product_url variable in quick add modal hyperlink

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

Render swatches in card__content

        {% if settings.card_product_swatch_enable %}
          {% render 'card-product-swatch', card_product: card_product %}
        {% endif %}

Add new swatch class in card__information

{% if settings.card_product_swatch_enable %}card__information--swatch{% endif %}

Add additional parameter card_product_user_selected in the price render

{% render 'price', product: card_product, price_class: '', show_compare_at_price: true, card_product_user_selected: card_product_user_selected %}

Browse other ways to boost conversion rate & profit

Here's 4 Ways We Can Help You

Here's 4 ways we can help you:

  1. Get our high-converting theme that generated us $4.3M in sales: Click here
  2. Find Conversion Leaks that cost you orders and profit: Click here
  3. Work with us to add $10,000 to your store in 10 weeks. Send an email to hello [at] theprompted.co with the subject line “10k”
  4. Email us to work privately on custom development. Send an email to hello [at] theprompted.co and use the subject line "custom", and then detail out what you have in mind, including all specifics.