If your Shopify variant selector is not working — the options won’t switch, or the price, image and Add to cart button don’t change when you pick a size or colour — it’s almost always one of four things: the JavaScript isn’t bound to the current option inputs, the section HTML is stale after a theme or app change, the product JSON the script reads is missing or altered, or an app conflict is intercepting the selection. Variant selection in Shopify is pure client-side JavaScript, so the fastest way to find the real cause is to open your browser console on the product page — the error is usually sitting right there, pointing at the exact line that broke.
How Shopify variant selection actually works
Understanding the chain makes every fix obvious. On a product page, selecting a variant runs a small, predictable sequence:
- Option inputs — radio buttons, a
<select>, or swatch labels — hold the current choice (e.g. Size = M, Colour = Blue). - A
changeevent fires when the shopper picks an option. - A theme script reads all selected options and looks up the matching variant in the
product JSON — a
<script type="application/json">block (orproduct.variants) rendered into the page by Liquid. - If it finds a match, it updates the price, the featured image, availability, the
hidden variant ID input, and the URL (
?variant=123…).
Break any single link — the event doesn’t fire, the JSON is missing, the lookup fails — and the visible result is the same: the picker looks fine but nothing updates. That’s why “variant selector not working” has so many different root causes behind identical symptoms.
Why it matters
A broken variant selector isn’t cosmetic. If the price doesn’t update, shoppers distrust the total. If the image doesn’t change, they aren’t sure they’re buying the right thing. Worst of all, if the hidden variant ID never updates, the wrong variant — or a sold-out one — gets added to cart, or Add to cart silently fails. Every one of those blocks a purchase on your highest-intent page. This is the same family of problem as an Add to cart button not working: the storefront looks live but the buying flow is quietly dead.
The likely causes
1. JavaScript not bound to the current inputs. Theme scripts attach their change
listener to specific selectors (a class, a variant-selects custom element). If a theme
update, a section edit or an app renamed, wrapped or duplicated those elements, the
listener is now attached to nothing — or to stale markup. The picker renders, but no code
is listening.
2. Stale section HTML after section rendering or an app change. Modern themes use the Section Rendering API to re-fetch and swap product-page HTML (for quick-view, variant changes, or app injections). If the swapped-in HTML doesn’t get re-initialised, the new inputs have no bound JavaScript. Similarly, a leftover element from a removed app or a half-applied theme edit can leave two conflicting pickers in the DOM.
3. Missing or altered product JSON. The lookup step depends on that JSON block being
present, complete and valid. If a customization removed it, a Liquid edit broke it (a
stray comma, an unclosed tag), or an app replaced it with its own shape, the script throws
when it tries to parse or read variants — and stops.
4. Combined listings quirks. Shopify combined listings merge several products into
one option experience. The variant data spans multiple products, so a theme (or app) that
assumes one flat product.variants array can mis-map or fail to find the selected combo —
the selector appears to do nothing on certain option paths.
5. App block or custom swatch conflict. Colour-swatch, bundle, upsell and
product-options apps frequently replace the native picker or hook its events. Two
scripts both trying to own the change event, or a custom swatch that updates its own UI
but never dispatches the event the theme listens for, will break selection. If it broke
right after installing or updating an app, start there — see our guide on diagnosing a
Shopify app conflict.
6. Load order and JavaScript errors. If an app or custom snippet throws an error before the variant script runs, the browser stops executing the rest of the file and the picker never initialises. One unrelated error high on the page can take down everything below it.
How to diagnose it — the checklist
Work top to bottom; stop when the page tells you the cause.
- Open the browser console (F12 → Console) on the product page. A red error naming a
file or
undefinedproperty usually is the answer. Note whether it’s from the theme or an app’s script. - Check the Network tab when you change a variant. Does a section-render request fire? Does it 404 or return partial HTML?
- Confirm the product JSON. In the Elements/Inspector, find the
<script type="application/json">variant block. Is it present and valid? Paste it into a JSON validator if unsure. - Verify the event binding. In the console, select an option and watch for updates. If nothing logs and nothing changes, the listener isn’t attached to the live inputs.
- Test with apps disabled on a duplicate theme. Duplicate the theme, remove or disable the suspect app blocks there, and retest. If the selector works, you’ve isolated an app conflict without touching your live store.
- Check for combined listings. If only certain products or option paths fail, look at whether they’re part of a combined listing.
How to fix each cause
- Unbound JS / stale sections: re-initialise the variant script after any HTML swap (re-run the picker’s setup on the new nodes, or re-register the custom element). Restore the exact selectors/classes the theme’s script expects.
- Broken product JSON: repair the Liquid that renders the variant JSON so it outputs valid, complete data. If an app replaced it, make the theme read the source the app now provides — or remove the override.
- Combined listings: use a theme (or code) path that reads variant data across the combined set rather than assuming one flat array.
- App / swatch conflict: fix load order, stop the double-binding, and make custom
swatches dispatch the same
changeevent the theme listens for so price and image still update. Sometimes the clean fix is removing the redundant app. - JavaScript errors: fix the erroring script (or defer it) so the variant code runs.
Common mistakes
- Editing the live theme. Never debug variant JS on your published theme — one bad save breaks selling for every visitor. Always work on a duplicate and preview.
- Guessing instead of reading the console. The error message is specific; guessing at fixes usually adds new breakage on top of the original.
- Fixing the symptom, not the cause. Hard-coding a price or hiding the picker “so it looks fine” leaves the wrong variant going to cart. Fix the binding or the JSON.
- Ignoring what changed. Selectors rarely break on their own. A theme update or app install is nearly always the trigger — if a theme update broke your custom code, that’s your starting point.
When to get a developer
If the console error points into minified theme or app code, if combined listings are involved, or if the fix means re-initialising section-rendered HTML, this is proper front-end debugging — the wrong edit can break checkout for everyone. That’s exactly the kind of work our Shopify development service and theme customization handle day to day: finding whether it’s the theme JS, the product JSON or an app, and fixing it safely on a duplicate before it goes live. Not sure where the leak is? A free profit audit will pinpoint it.
Variant picker broken after a theme or app change? Send us your store URL and the product page — we’ll find whether it’s the theme JS, the product JSON or an app conflict, and fix it fast. See our Shopify development service or get a free profit audit.