Skip to content
Combine Separate Products Together As Variant Options (v3) - Free Tutorial
Browse other ways to boost conversion rate & profit

Combine Separate Products Together As Variant Options (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.

---

Two of our most popular customizations are our product swatches and our product-linked variants, which combine separate products like they are variants. This helps better organize your products or gets around the 100 variant limit if you have many related variants of a product.

Many of you have used both, but were wondering whether you can add swatches to these new product-linked variants. 

Well that’s exactly what I’ll be showing you in this tutorial! We’re going to go over a quick demo of what the customization looks like and then we’ll run through step by step how to add it.

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

Links:

 

 

Create Metaobject and Metafield

Repeat these steps for each Option Type desired.

Below is an example.

Create a metaobject

Name: Product Grouping Option 1 Entries

Fields:

  • Type: Single line text
    • Name: Grouping Name
  • Type: List of products
    • Name: Product Grouping

Create Product Metafield with type Metaobject above

Note: this step was accidentally cut out of the video tutorial.

This metafield is used to define the metaobject that groups

Name: Product Grouping Option 1

Type: Metaobject (Product Grouping Option 1 Entries)

Create Product Metafield with type Single Line Text

This metafield is used to define the product variant's name.

Name: Product Grouping Option 1 Value

Type: Single Line Text

Create the Metaobject Entry 

Create the metaobject entry and select the products to be grouped together. Each of the products to be grouped together must then have their Product Grouping Option metafield set to this metaobject entry.

Also create the JSON reference. Below is an example structure. The variant_value must match the desired Product Grouping Option Value.

[
  {
    "variant_name": "Style",
    "variant_value": "T-Shirt",
    "variant_swatch": "t-shirt.jpg"
  }
]

Edit Theme Code

Note: unlike in the video, product listing swatches are now set independently from the variant picker swatch settings. For example, swatch size and shape for the product grouping picker can be square and variant picker swatches can be circles.

Create new snippet product-grouping-picker-custom.liquid

{%- if product.metafields.custom[block.settings.option_type_metafield_key] -%} 
  
  {% if product.has_only_default_variant %}
    {{ 'component-product-variant-swatch-custom.css' | asset_url | stylesheet_tag }}
    {{ 'component-product-variant-picker.css' | asset_url | stylesheet_tag }}
  {% endif %}

  
  {% assign related_products_metaobject = product.metafields.custom[block.settings.option_type_metafield_key].value %}
  {% assign related_products = related_products_metaobject.product_grouping.value %}

  {% 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.combined_listing_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 %}

  {%- if block.settings.picker_type == 'button' -%}

    {% if block.settings.use_swatch_custom == false %}
      
      <fieldset class="js product-form__input product-form__input--pill">
        <legend class="form__label">{{ block.settings.grouping_label }}</legend>
        {% for related_product in related_products %}
          <input
            type="radio"
            id="related_product-{{ related_product.id }}-{{ block.id }}"
            name="related_product_{{ block.id }}"
            value="{{ related_product.url }}"
            onchange="window.location.href=this.value;"
            {% if product.handle == related_product.handle %}
              checked="checked"
            {% endif %}
          >
          <label for="related_product-{{ related_product.id }}-{{ block.id }}">{{ related_product.metafields.custom[block.settings.option_value_metafield_key].value }}</label>
        {% endfor %}
      </fieldset>
  
    {% else %}
      
      {% assign variant_options_images = variant_images_data.value | where: "variant_name", block.settings.grouping_label %}
      <fieldset class="js product-form__input product-form__input--pill">
        {% assign base_store_files_url = '//' | append: shop.permanent_domain | append: '/cdn/shop/files/' %}
        {% for related_product in related_products %}
          {% if product.handle == related_product.handle %}
            <legend class="form__label">
              {{ block.settings.grouping_label }}:
              <span id="selected{{ block.settings.grouping_label }}">{{ related_product.metafields.custom[block.settings.option_value_metafield_key].value }}</span>
            </legend>
          {% endif %}
          {% assign variant_image_url = nil %}
          {% assign option_disabled = true %}
          {% if related_product.available %}
            {% assign option_disabled = false %}
          {% endif %}
          {% assign swatch_found = false %}
          {% for item in variant_options_images %}
            {% if item.variant_value == related_product.metafields.custom[block.settings.option_value_metafield_key].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 %}
          {% unless swatch_found %}
            {% assign variant_image_url = related_product.featured_image.src | img_url: '100x100' %}
          {% endunless %}
           <div class="product-form__swatch"> 
              <input
                type="radio"
                id="related_product-{{ related_product.id }}-{{ block.id }}"
                name="related_product_{{ block.id }}"
                value="{{ related_product.url }}"
                onchange="window.location.href=this.value;"
                data-product-id="{{ product.id }}" 
                data-image-url="{{ variant_image_url }}"
                {% if product.handle == related_product.handle %}
                  checked="checked"
                {% endif %}
                {% if option_disabled %}
                  class="disabled"
                {% endif %}
              >
              <label for="related_product-{{ related_product.id }}-{{ block.id }}" style="background-image: url('{{ variant_image_url }}'); width: {{ block.settings.combined_listing_swatch_size }}px; height: {{ block.settings.combined_listing_swatch_size }}px; border-radius: {% if block.settings.combined_listing_swatch_shape_custom == 'circle' %}50%{% else %}0%{% endif %} !important;">
                <span class="visually-hidden">{{ 'products.product.variant_sold_out_or_unavailable' | t }}</span>
              </label>
           </div>
         {% endfor %}
      </fieldset>
      
    {% endif %}
    
  {%- elsif block.settings.picker_type == 'dropdown' -%}
    <div class="product-form__input product-form__input--dropdown">
      <label for="ProductSelect-related-{{ section.id }}" class="form__label">{{ block.settings.grouping_label }}</label>
      <div class="select">
        <select
          id="ProductSelect-related-{{ section.id }}-{{ block.id }}"
          class="select__select"
          onchange="window.location.href=this.value;"
        >
          {% for related_product in related_products %}
            <option
              value="{{ related_product.url }}"
              {% if product.handle == related_product.handle %}
                selected="selected"
              {% endif %}
            >
              {{ related_product.metafields.custom[block.settings.option_value_metafield_key].value }}
            </option>
          {% endfor %}
        </select>
        {% render 'icon-caret' %}
      </div>
    </div>
  {%- endif -%}
{%- endif -%}

Edit main-product.liquid

Add the option to select product grouping picker as a block

{%- when 'product_grouping_picker' -%}   
  {% render 'product-grouping-picker-custom', product: product, block: block %}

Add block options in the settings schema

    {
      "type": "product_grouping_picker",
      "name": "Product Grouping Picker",
      "limit": 3,
      "settings": [
        {
          "type": "text",
          "id": "heading",
          "default": "Product Grouping Picker",
          "label": "Product Grouping Picker Option"
        },        
        {
          "type": "select",
          "id": "picker_type",
          "options": [
            {
              "value": "dropdown",
              "label": "t:sections.main-product.blocks.variant_picker.settings.picker_type.options__1.label"
            },
            {
              "value": "button",
              "label": "t:sections.main-product.blocks.variant_picker.settings.picker_type.options__2.label"
            }
          ],
          "default": "button",
          "label": "t:sections.main-product.blocks.variant_picker.settings.picker_type.label"
        },
        {
          "type": "text",
          "id": "grouping_label",
          "default": "Grouping 1",
          "label": "Product Grouping Label"
        },
        {
          "type": "text",
          "id": "option_type_metafield_key",
          "label": "Option Metafield Key",
          "info": "Used for the grouping option",
          "default": "product_grouping_option_1"
        },
        {
          "type": "text",
          "id": "option_value_metafield_key",
          "label": "Option Value Metafield Key",
          "info": "Used for the grouping option value text",
          "default": "product_grouping_option_1_value"
        },
        {
          "type": "header",
          "content": "Custom Swatch"
        },
        {
          "id": "combined_listing_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": "combined_listing_variant_swatch_metaobject",
          "label": "Default Variant Swatch Map Metaobject",
          "default": "variant-swatch-mapping",
          "info": "Can be overridden with product metafield"
        },
        {
          "type": "range",
          "id": "combined_listing_swatch_size",
          "min": 30,
          "max": 60,
          "step": 1,
          "unit": "px",
          "label": "Swatch Size",
          "default": 40
        }
      ]
    },

Browse other ways to boost conversion rate & profit