Shopify variant picker
not working.
The variant picker stops updating price, swatches, the main image, or the hidden variant ID, and the wrong variant ends up in the cart. On Dawn 12+ and Horizon the cause is almost always a third-party app, not the theme. Five likely causes below, ranked by frequency, with the fix for each and a 30-second console diagnostic.
Symptoms
- Price doesn't change when a different variant is selected, even though variants have different prices.
- Featured image stays on the first variant after switching colour or size.
- Wrong variant added to cart: the customer picks size L but cart shows size M (or the first variant).
- URL
?variant=stops updating when the picker is clicked, breaking deep links and analytics. - Sold-out variant looks available: the ATC button stays enabled even on an out-of-stock combination.
30-second diagnostic
Run this in the browser console on the broken product page, then click variants. The output tells you which cause below applies.
// Jelonyx · Shopify variant picker diagnostic
// Paste into devtools console on a product page.
// Click each variant after running: watch the output.
(() => {
const form = document.querySelector('form[action*="/cart/add"]');
const idInput = form && form.querySelector('input[name="id"]');
const radios = document.querySelectorAll('input[name^="options"], fieldset input[type="radio"]');
console.group('%c[jlx] variant picker check', 'color:#B8D67A;font-weight:600');
console.log('product form:', form ? '✅ found' : '❌ missing');
console.log('hidden id input:', idInput ? '✅ found, current = ' + idInput.value : '❌ missing');
console.log('variant inputs:', radios.length || '❌ none : picker is custom or removed');
console.groupEnd();
if (idInput) {
new MutationObserver(() => {
console.log('[jlx] hidden id changed →', idInput.value);
}).observe(idInput, { attributes: true, attributeFilter: ['value'] });
}
document.addEventListener('variant:change', (e) => {
console.log('[jlx] variant:change event →', e.detail || e);
});
document.addEventListener('on:variant:change', (e) => {
console.log('[jlx] on:variant:change event →', e.detail || e);
});
})();- No product form found → an app has replaced the form (cause #1) or the theme is non-standard (cause #4).
- Form found, hidden id never changes on click → the variant change handler is detached (cause #1, #2, or #3).
- Hidden id updates but the page doesn't → the theme is firing the legacy
variant:changeevent but the new theme listens foron:variant:change(cause #2). - No variant inputs at all → all variants are unavailable and the theme is hiding them (cause #5).
Most likely causes, ranked
- 01An installed app re-rendered the product form.
Bundle builders, sticky ATC apps, currency converters, custom product page builders, and some review apps replace the
<form>after Shopify's variant listener has bound to the original. The new form has no listener. Frequency: most common.Fix: in Shopify Admin → Apps, disable apps one at a time. Reload the product page after each. The picker starts working when the conflicting app is off. Either replace that app, contact its support for an OS 2.0 fix, or rebind the picker manually using the snippet at the bottom of this page.
- 02Theme is firing the wrong variant change event.
Older themes dispatch
variant:changeon the form. Dawn 12+ and Horizon listen foron:variant:changeondocument. After a partial theme update, custom Liquid can be left listening on the wrong event, so the price block updates and the image block doesn't (or the reverse).Fix: in
assets/global.jsor the relevant section's JS, search forvariant:change. If the theme is Dawn 12+ or Horizon, change it toon:variant:change. If you cannot edit theme JS, add the rebinder snippet below: it dispatches both events. - 03Custom Liquid hard-coded a variant ID.
A previous developer added something like
{% assign vid = product.selected_or_first_available_variant.id %}insidemain-product.liquidand reusedvidin a hidden input or data attribute. Liquid runs once per page, so the ID is frozen at the first variant and never updates.Fix: in the theme code editor, search
main-product.liquidforselected_or_first_available_variantand any custom hidden input namedidorvariant_id. Remove the duplicates. The form must contain exactly one<input name="id">. - 04Theme overridden by Shopify Section Rendering API.
Some themes re-fetch the product section on each variant click using the Section Rendering API. If the response 404s (because the section ID was renamed) or strips the form (because of a custom wrapper), the picker silently fails after the first click.
Fix: in the Network tab, click a variant and look for a request to
/products/[handle]?variant=...§ion_id=.... If it returns 404, fix the section ID reference in theme JS. If it returns 200 but the new HTML has no<form action="/cart/add">, remove the custom wrapper that's breaking the section. - 05All variants out of stock with "hide unavailable".
When the theme setting Hide variants when out of stock is on and every combination of options is sold out, the picker renders empty. Looks broken; is actually working as configured.
Fix: in the theme customiser → Product information block, toggle off Hide variants when out of stock, or restock at least one combination. To confirm this is the cause, set one variant to a stocked location with a positive quantity and reload.
The rebinder (causes #1, #2)
Use only when the diagnostic shows the form exists but the hidden ID never updates, and you can't remove the conflicting app. This script reads the picker state, finds the matching variant from the product JSON Shopify already renders, updates the hidden ID, and dispatches both legacy and modern variant change events so price, image, and ATC blocks all react.
{% comment %}
Jelonyx · Variant picker rebinder
Use only when an app or theme update has detached the variant change handler.
Drop into snippets/jlx-variant-rebind.liquid and render at the bottom of
sections/main-product.liquid (above {% schema %}).
Source: jelonyx.com/shopify/fix/variant-picker-not-working
{% endcomment %}
{%- if product != blank -%}
<script>
(function () {
'use strict';
var form = document.querySelector('form[action*="/cart/add"]');
if (!form) return;
var idInput = form.querySelector('input[name="id"]');
if (!idInput) return;
// Build a variant lookup from the product JSON Shopify already renders
var variants = {{ product.variants | json }};
function readSelected() {
var picked = [];
form.querySelectorAll('fieldset').forEach(function (fs) {
var checked = fs.querySelector('input:checked');
var select = fs.querySelector('select');
if (checked) picked.push(checked.value);
else if (select) picked.push(select.value);
});
return picked;
}
function findVariant(picked) {
return variants.find(function (v) {
return v.options.every(function (opt, i) { return opt === picked[i]; });
});
}
function update() {
var v = findVariant(readSelected());
if (!v) return;
if (idInput.value !== String(v.id)) {
idInput.value = v.id;
idInput.dispatchEvent(new Event('change', { bubbles: true }));
}
var url = new URL(window.location.href);
url.searchParams.set('variant', v.id);
window.history.replaceState({}, '', url.toString());
document.dispatchEvent(new CustomEvent('variant:change', { detail: { variant: v } }));
}
form.addEventListener('change', function (e) {
if (e.target.matches('input, select')) update();
});
})();
</script>
{%- endif -%}Render at the bottom of sections/main-product.liquid on the line directly above {% schema %}:
{% render 'jlx-variant-rebind' %}When to call a developer
- The diagnostic shows no product form at all and no app disable fixes it: the theme has been heavily customised and the form is built dynamically. Worth a paid hour, not a DIY.
- Section Rendering API responses are mangled: fixing a renamed section ID requires reading the theme's JS bundle, not just the Liquid.
- The picker works on desktop but not mobile: almost always a CSS event-blocker (a transparent overlay, a sticky header, a chat widget) catching the click. Needs hands-on debugging.
- Variants live in metafields, not native options:the fix is structurally different and not covered here.
Need this fixed today?
Variant pickers break stores quietly: wrong variant in cart, wrong revenue, wrong refunds. We diagnose the exact cause on your store, install the fix, and verify in the cart in one fixed-scope sprint.