Discuss and resolve questions on Liquid, JavaScript, themes, sales channels, and site speed enhancements.
I need to separate the color option from the rest of the product options in the theme customizer. This way the client can have control of having the color option as a button and the other options as dropdowns. The code below is what I have so far. I tried doing a for loop with the option name == 'color' but for some reason, it is looping in the other variants and duplicating them like in the image below. I have also added an image of what the customizer layout is now, so you can see that the Color has a separate option.
{%- unless product.has_only_default_variant -%}
{%- for product_option in product.options_with_values -%}
{%- if product_option.name == "Color" -%}
{%- if block.settings.color_picker_type == 'button' -%}
<variant-radios class="no-js-hidden" data-section="{{ section.id }}" data-url="{{ product.url }}" {{ block.shopify_attributes }}>
{%- for option in product.options_with_values -%}
<fieldset class="js product-form__input">
<legend class="form__label">{{ option.name }}</legend>
{%- for value in option.values -%}
<input type="radio" id="{{ section.id }}-{{ option.position }}-{{ forloop.index0 }}"
name="{{ option.name }}"
value="{{ value | escape }}"
form="{{ product_form_id }}"
{% if option.selected_value == value %}checked{% endif %}
>
{% if product.variants[forloop.index0].metafields.color.values and option.name == 'Color' %}
<label id="color" for="{{ section.id }}-{{ option.position }}-{{ forloop.index0 }}" style="background-color: {{product.variants[forloop.index0].metafields.color.values}}">
</label>
{% else %}
<label for="{{ section.id }}-{{ option.position }}-{{ forloop.index0 }}">
{{ value }}
</label>
{% endif %}
<style>
{% if product.variants[forloop.index0].metafields.color.values and option.name == 'Color' %}
.product-form__input input[type=radio]:checked+#color {
padding: 0!important;
height: 35px;
border-radius: 100%;
width: 35px;
outline: 1px solid #000!important;
outline-offset: 5px;
margin: 8px 0 0 !important;
vertical-align: bottom;
}
.product-form__input input[type=radio]+#color {
padding: 0px!important;
height: 35px;
border-radius: 100%;
width: 35px;
outline: none!important;
outline-offset: 5px;
margin: 12px 6px 0!important;
}
{% endif %}
</style>
{%- endfor -%}
</fieldset>
{%- endfor -%}
<script type="application/json">
{{ product.variants | json }}
</script>
</variant-radios>
{%- else -%}
<variant-selects class="no-js-hidden" data-section="{{ section.id }}" data-url="{{ product.url }}" {{ block.shopify_attributes }}>
{%- for option in product.options_with_values -%}
<div class="product-form__input product-form__input--dropdown">
<label class="form__label" for="Option-{{ section.id }}-{{ forloop.index0 }}">
{{ option.name }}
</label>
<div class="select">
<select id="Option-{{ section.id }}-{{ forloop.index0 }}"
class="select__select"
name="options[{{ option.name | escape }}]"
form="{{ product_form_id }}"
>
{%- for value in option.values -%}
<option value="{{ value | escape }}" {% if option.selected_value == value %}selected="selected"{% endif %}>
{{ value }}
</option>
{%- endfor -%}
</select>
{% render 'icon-caret' %}
</div>
</div>
{%- endfor -%}
<script type="application/json">
{{ product.variants | json }}
</script>
</variant-selects>
{%- endif -%}
{%- else -%}
{%- if block.settings.picker_type == 'button' -%}
<variant-radios class="no-js-hidden" data-section="{{ section.id }}" data-url="{{ product.url }}" {{ block.shopify_attributes }}>
{%- for option in product.options_with_values -%}
<fieldset class="js product-form__input">
<legend class="form__label">{{ option.name }}</legend>
{%- for value in option.values -%}
<input type="radio" id="{{ section.id }}-{{ option.position }}-{{ forloop.index0 }}"
name="{{ option.name }}"
value="{{ value | escape }}"
form="{{ product_form_id }}"
{% if option.selected_value == value %}checked{% endif %}
>
{%- endfor -%}
</fieldset>
{%- endfor -%}
<script type="application/json">
{{ product.variants | json }}
</script>
</variant-radios>
{%- else -%}
<variant-selects class="no-js-hidden" data-section="{{ section.id }}" data-url="{{ product.url }}" {{ block.shopify_attributes }}>
{%- for option in product.options_with_values -%}
<div class="product-form__input product-form__input--dropdown">
<label class="form__label" for="Option-{{ section.id }}-{{ forloop.index0 }}">
{{ option.name }}
</label>
<div class="select">
<select id="Option-{{ section.id }}-{{ forloop.index0 }}"
class="select__select"
name="options[{{ option.name | escape }}]"
form="{{ product_form_id }}"
>
{%- for value in option.values -%}
<option value="{{ value | escape }}" {% if option.selected_value == value %}selected="selected"{% endif %}>
{{ value }}
</option>
{%- endfor -%}
</select>
{% render 'icon-caret' %}
</div>
</div>
{%- endfor -%}
<script type="application/json">
{{ product.variants | json }}
</script>
</variant-selects>
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{%- endunless -%}
Hi @michaelmorgan,
Please change all code:
{%- unless product.has_only_default_variant -%}
{%- for option in product.options_with_values -%}
{%- if option.name == "Color" -%}
<variant-radios class="no-js-hidden" data-section="{{ section.id }}" data-url="{{ product.url }}" {{ block.shopify_attributes }}>
<fieldset class="js product-form__input">
<legend class="form__label">{{ option.name }}</legend>
{%- for value in option.values -%}
<input type="radio" id="{{ section.id }}-{{ option.position }}-{{ forloop.index0 }}"
name="{{ option.name }}"
value="{{ value | escape }}"
form="{{ product_form_id }}"
{% if option.selected_value == value %}checked{% endif %}
>
{% if product.variants[forloop.index0].metafields.color.values and option.name == 'Color' %}
<label id="color" for="{{ section.id }}-{{ option.position }}-{{ forloop.index0 }}" style="background-color: {{product.variants[forloop.index0].metafields.color.values}}">
</label>
{% else %}
<label for="{{ section.id }}-{{ option.position }}-{{ forloop.index0 }}">
{{ value }}
</label>
{% endif %}
<style>
{% if product.variants[forloop.index0].metafields.color.values and option.name == 'Color' %}
.product-form__input input[type=radio]:checked+#color {
padding: 0!important;
height: 35px;
border-radius: 100%;
width: 35px;
outline: 1px solid #000!important;
outline-offset: 5px;
margin: 8px 0 0 !important;
vertical-align: bottom;
}
.product-form__input input[type=radio]+#color {
padding: 0px!important;
height: 35px;
border-radius: 100%;
width: 35px;
outline: none!important;
outline-offset: 5px;
margin: 12px 6px 0!important;
}
{% endif %}
</style>
{%- endfor -%}
</fieldset>
<script type="application/json">
{{ product.variants | json }}
</script>
</variant-radios>
{%- else -%}
<variant-selects class="no-js-hidden" data-section="{{ section.id }}" data-url="{{ product.url }}" {{ block.shopify_attributes }}>
<div class="product-form__input product-form__input--dropdown">
<label class="form__label" for="Option-{{ section.id }}-{{ forloop.index0 }}">
{{ option.name }}
</label>
<div class="select">
<select id="Option-{{ section.id }}-{{ forloop.index0 }}"
class="select__select"
name="options[{{ option.name | escape }}]"
form="{{ product_form_id }}"
>
{%- for value in option.values -%}
<option value="{{ value | escape }}" {% if option.selected_value == value %}selected="selected"{% endif %}>
{{ value }}
</option>
{%- endfor -%}
</select>
{% render 'icon-caret' %}
</div>
</div>
<script type="application/json">
{{ product.variants | json }}
</script>
</variant-selects>
{%- endif -%}
{%- endfor -%}
{%- endunless -%}
Hope it helps!
That works! thank you!
Hey, I have implemented the code but now I notice that now the variant selector (dropdown) isn't connected with the variant options. I think it has something to do with the for loop. It doesn't change the product image and the price disappears. Here is the JS that I believe applies to the variant dropdown.
class VariantSelects extends HTMLElement {
constructor() {
super();
this.addEventListener('change', this.onVariantChange);
}
onVariantChange() {
this.updateOptions();
this.updateMasterId();
this.toggleAddButton(true, '', false);
this.updatePickupAvailability();
this.removeErrorMessage();
if (!this.currentVariant) {
this.toggleAddButton(true, '', true);
this.setUnavailable();
} else {
this.updateMedia();
this.updateURL();
this.updateVariantInput();
this.renderProductInfo();
this.updateShareUrl();
}
}
updateOptions() {
this.options = Array.from(this.querySelectorAll('select'), (select) => select.value);
}
updateMasterId() {
this.currentVariant = this.getVariantData().find((variant) => {
return !variant.options.map((option, index) => {
return this.options[index] === option;
}).includes(false);
});
}
updateMedia() {
if (!this.currentVariant) return;
if (!this.currentVariant.featured_media) return;
const mediaGallery = document.getElementById(`MediaGallery-${this.dataset.section}`);
mediaGallery.setActiveMedia(`${this.dataset.section}-${this.currentVariant.featured_media.id}`, true);
const modalContent = document.querySelector(`#ProductModal-${this.dataset.section} .product-media-modal__content`);
const newMediaModal = modalContent.querySelector( `[data-media-id="${this.currentVariant.featured_media.id}"]`);
modalContent.prepend(newMediaModal);
}
updateURL() {
if (!this.currentVariant || this.dataset.updateUrl === 'false') return;
window.history.replaceState({ }, '', `${this.dataset.url}?variant=${this.currentVariant.id}`);
}
updateShareUrl() {
const shareButton = document.getElementById(`Share-${this.dataset.section}`);
if (!shareButton) return;
shareButton.updateUrl(`${window.shopUrl}${this.dataset.url}?variant=${this.currentVariant.id}`);
}
updateVariantInput() {
const productForms = document.querySelectorAll(`#product-form-${this.dataset.section}, #product-form-installment`);
productForms.forEach((productForm) => {
const input = productForm.querySelector('input[name="id"]');
input.value = this.currentVariant.id;
input.dispatchEvent(new Event('change', { bubbles: true }));
});
}
updatePickupAvailability() {
const pickUpAvailability = document.querySelector('pickup-availability');
if (!pickUpAvailability) return;
if (this.currentVariant && this.currentVariant.available) {
pickUpAvailability.fetchAvailability(this.currentVariant.id);
} else {
pickUpAvailability.removeAttribute('available');
pickUpAvailability.innerHTML = '';
}
}
removeErrorMessage() {
const section = this.closest('section');
if (!section) return;
const productForm = section.querySelector('product-form');
if (productForm) productForm.handleErrorMessage();
}
renderProductInfo() {
fetch(`${this.dataset.url}?variant=${this.currentVariant.id}§ion_id=${this.dataset.section}`)
.then((response) => response.text())
.then((responseText) => {
const id = `price-${this.dataset.section}`;
const html = new DOMParser().parseFromString(responseText, 'text/html')
const destination = document.getElementById(id);
const source = html.getElementById(id);
if (source && destination) destination.innerHTML = source.innerHTML;
const price = document.getElementById(`price-${this.dataset.section}`);
if (price) price.classList.remove('visibility-hidden');
this.toggleAddButton(!this.currentVariant.available, window.variantStrings.soldOut);
});
}
toggleAddButton(disable = true, text, modifyClass = true) {
const productForm = document.getElementById(`product-form-${this.dataset.section}`);
if (!productForm) return;
const addButton = productForm.querySelector('[name="add"]');
const addButtonText = productForm.querySelector('[name="add"] > span');
if (!addButton) return;
if (disable) {
addButton.setAttribute('disabled', 'disabled');
if (text) addButtonText.textContent = text;
} else {
addButton.removeAttribute('disabled');
addButtonText.textContent = window.variantStrings.addToCart;
}
if (!modifyClass) return;
}
setUnavailable() {
const button = document.getElementById(`product-form-${this.dataset.section}`);
const addButton = button.querySelector('[name="add"]');
const addButtonText = button.querySelector('[name="add"] > span');
const price = document.getElementById(`price-${this.dataset.section}`);
if (!addButton) return;
addButtonText.textContent = window.variantStrings.unavailable;
if (price) price.classList.add('visibility-hidden');
}
getVariantData() {
this.variantData = this.variantData || JSON.parse(this.querySelector('[type="application/json"]').textContent);
return this.variantData;
}
}
customElements.define('variant-selects', VariantSelects);
Hi @michaelmorgan,
I checked and it needs to change for JS so this would be a complicated requirement.
I recommend hiring an expert or installing an app for it.
Because it takes a lot of time to change, debug and test, it will be difficult for me to guide you in detail.
Please sympathize with me in this case.
@LitExtension When products have multiple options then this code {{ product.variants[forloop.index0].metafields.color.values }} is not working and print duplicate values
Shopify and our financial partners regularly review and update verification requiremen...
By Jacqui Mar 14, 2025Unlock the potential of marketing on your business growth with Shopify Academy's late...
By Shopify Mar 12, 2025Learn how to increase conversion rates in every stage of the customer journey by enroll...
By Shopify Mar 5, 2025