Multiple Variant Images

Different images for variants in the Horizon theme from Shopify (without apps and without Shopify Plus). A free solution from VecomLab.

Relevant for Horizon theme version 3.1.0


circle-info

If you want to avoid coding, try the Shrinearrow-up-right theme.

Shrinearrow-up-right offers over 120 built-in features that help improve conversion rates and increase the average order value, without the need to configure additional apps or pay for subscriptions. Everything is customizable without code, saving you time and resources.

torii-gateUse Shrine โˆ’ 15% OFF

Your discount code: VECOMLAB

Problem description

By default, in the Shopify admin and in the Horizon theme settings, each variant can only be assigned one image โ€” not several.

The platform partially solves this for stores on the Plus plan. You can also use third-party Shopify apps, but this is often unprofitable and inefficient: errors appear regularly and sometimes take months to fix via support.

You can solve this task using Metafields and Metaobjects for Variants and the theme code editor.

Idea

  • Each variant has a metafield custom.variant_gallery (list of files).

  • We treat as โ€œownedโ€ all images whose file name matches the stem from the metafield or starts with the stem + _ or -.

  • On the server (Liquid) we pre-hide extra images for the first frame.

  • On the client (JS) we read the โ€œvariant โ†’ filesโ€ map and filter when the variant changes.

  • If none of the variants has the metafield โ€” nothing changes, everything works as in the original theme.

Whatโ€™s new: zoom and quick add

  1. The zoom dialog is now also filtered by variant: both thumbnails and large images.

  2. Mobile quick add (collection page modal): on mobile, the modal shows only one image โ€” the featured image of the current variant (as in the standard Horizon), even if the variant has a whole series.

Step 1. Variant metafield (once per store)

Settings โ†’ Metafields and metaobjects โ†’ Variants โ†’ Add definition

  • Name: Variant Gallery

  • Namespace and key: custom.variant_gallery

  • Type: File โ†’ List of files

  • Validations: Images only

  • Options: enable Storefront API access

  • Save

Why this way: this is the native way to link a variant with files in Shopify Files. The key matches what the code uses.

Step 2. File name convention

In Files, give each series of photos for a variant a common stem:

In the variant metafield attach one โ€œanchorโ€ file from the series (for example, ring-gold.jpg). The rest will be picked up automatically by the stem.

Step 3. Inserts in _product-media-gallery.liquid

Below is exactly what we added/changed, and why.

3.1. JSON map variant โ†’ files (server โ†’ front)

Insert before {%- if has_image_drop -%}:

Why: the front receives ready JSON without parsing the DOM. We immediately normalize URLs to the base file name.

In the tag attributes, next to data-presentation and style, add:

  • data-vg-ready="0" โ€” anti-flicker: while the script is running, images are hidden (CSS below).

  • data-initial-variant-id โ€” optional, for debugging/analytics; the script itself takes the variant id from the form.

circle-info

If you want things maximally โ€œcleanโ€, you can remove data-initial-variant-id. Functionality wonโ€™t change.

3.3. vg_list for the first render

Right after {{ block.shopify_attributes }}:

Why: we need to know the current variantโ€™s file list on the server in order to pre-hide extra images even before JS runs (correct first frame).

Before you compute class/style/attributes for each media:

Then add vg-prehide and display:none:

3.5. Unified set of data attributes on each slide

Before the block with attributes:

And replace the attributes block with options that inject {{- common_data_attrs -}} (for model / image / โ€œno zoomโ€).

Before:

After:

Why: the front needs the image type and URL. data-vg-allow/data-vg-prehide help CSS before JS initializes.

3.6. Same logic โ€” for the Grid

Inside <ul class="media-gallery__grid"> before each <li> repeat the prehide logic:

And in the Grid <li> replace class="" and style="" with:

Add data attributes:

3.7. Filtering inside the zoom dialog (Liquid)

Zoom thumbnails (1)

Inside the thumbnails block (buttons .dialog-thumbnails-list__thumbnail) add pre-hide logic, like we did for slides/grid:

In the <button> replace class="" and style="":

And add attributes:

Large zoomed frames (2)

For <li> inside .dialog-zoomed-gallery apply the same prehide check and the same data attributes.

Insert the check before <li>:

Replace class="" and style="" in <li>:

And add attributes:

Same idea: we ensure a โ€œcleanโ€ first frame in zoom even before JS runs.

Script:

  • reads #variant-galleries-json;

  • if the product has no variants with the metafield โ†’ sets data-vg-ready="1" and exits (changes nothing);

  • otherwise filters image slides by the current variant; on variant change โ€” repeats;

  • at the end sets data-vg-ready="1" (anti-flicker off).

CSS:

Why: completely removes โ€œjitter/flickerโ€ and intermediate states before everything is ready. We donโ€™t touchthumbnails/arrows โ€” in the โ€œcleanโ€ version of this script we donโ€™t even hide them.

Why this is needed:

  • while data-vg-ready="0" is set, the modal doesnโ€™t โ€œflashโ€ with a set of images;

  • JS additionally guarantees that even after switching variants in the modal there will be exactly one image.

What we deliberately donโ€™t touch (remains โ€œas in the themeโ€)

Weโ€™ll do this in future script versions:

  • Counter / arrows / thumbnails of the carousel โ€” we donโ€™t configure or hide them with separate rules.

Small audit of โ€œextra stuffโ€

  • data-initial-variant-id โ€” optional, can be removed (wonโ€™t break the script).

  • MutationObserver is currently attached to document.body for robustness with dynamic re-renders. You can narrow it to only watch <media-gallery>, but the current option is more reliable across themes/scripts.

  • The duplicated logic โ€œserver-side pre-hideโ€ + โ€œclient-side filterโ€ is intentional: it gives an ideal first frame (no junk) and correct behavior on further switches. This is not redundant; itโ€™s an anti-FOUC layer.

Result

If a variant has the Variant Gallery metafield filled in, only its images are shown on the page (by file name). If there is no metafield โ€” everything works as before. The first frame is clean and without flickering (server pre-hide), and when switching variants the gallery is updated by the script. No apps, conflicts, or slowdowns.

Theme settings

The Product media (Grid + Hint) and Grid + Dots modes are supported. The โ€œsingle image in Mobile quick addโ€ behavior is enabled only inside the modal on screens โ‰ค 749 px; on the product page and on desktop you still have the full variant-based gallery.

FAQ

chevron-rightWhat exactly do I need to configure in the admin?hashtag

Only one thing: create a variant metafield custom.variant_gallery of type File โ†’ List of files (Images only) and enable Storefront API access. Everything else is inserts into a single template, _product-media-gallery.liquid.

chevron-rightHow many files should I attach to the variant metafield?hashtag

One anchor file from the series is enough. The rest will be picked up automatically by the name (by the โ€œstemโ€) if they are called stem.jpg, stem_2.jpg, stem-3.jpg, etc.

chevron-rightHow exactly should the file be named?hashtag

All photos of one modification should share a common stem (base of the file name): ring-gold.jpg, ring-gold_2.jpg, ring-gold-3.jpg. Matching goes by exact stem or stem with _ / - suffix. Case and Shopify sizes (_800x) donโ€™t matter โ€” the script strips them.

chevron-rightWhat if the variant metafield is empty?hashtag

Then all product images are shown for that variant (as in the original theme). The logic only kicks in if at least one attached image is found for the variant.

chevron-rightWhat happens if none of the variants has the metafield?hashtag

The script detects this, does nothing, and immediately sets data-vg-ready="1". The gallery works exactly as in the theme, with no extra work or performance impact.

chevron-rightWill images โ€œflickerโ€ on load or when switching variants?hashtag

No. We used a double protection:

  • on the Liquid side โ€” pre-hiding extra images for the first frame;

  • on the JS side โ€” filtering on variant switches + the data-vg-ready flag with mini CSS to avoid intermediate states on screen.

chevron-rightIn the zoom dialog all photos are shown. Is that intentional?hashtag

No, now zoom is also filtered. Both thumbnails and large images in the dialog show only the current variantโ€™s images (by the same stem rule). The first frame is clean thanks to Liquid pre-hide; then JS takes over.

chevron-rightIn what cases might the script โ€œnot seeโ€ the variant id?hashtag

If the theme/app renders the variant form in a non-standard way. The script looks for name="id" in the add-to-cart form. If that field is renamed or missing, update the selector in getVariantId() or dispatch a custom selectVariant/variant:change event with detail.variant.id.

chevron-rightI have a โ€œquick viewโ€ / product modal โ€” will this work there?hashtag

Usually yes, because the script uses a MutationObserver on document.body and reapplies the filter. If the modal loads content dynamically, make sure the #variant-galleries-json block ends up inside its DOM.

chevron-rightWhat about performance?hashtag
  • The JSON block is built once on the server;

  • JS only walks visible gallery elements and uses simple string checks;

  • If there are no metafields โ€” the script effectively โ€œturns offโ€. Thereโ€™s no overhead on pages without metafields.

chevron-rightWill SEO/alt texts/structured data be affected?hashtag

We donโ€™t remove elements, we only add display:none to the initial state and toggle visibility. Alt texts and schema.org generated by the theme stay intact. The set of images in the DOM remains the same; some are just hidden when the variant is different.

chevron-rightDo I need to change the media order in the product itself?hashtag

Not necessarily. But for better UX, the โ€œanchorโ€ photo of each variant (that stem) should ideally sit near its series, so pre-hide and the first frame look visually logical.

chevron-rightWhat if different variants accidentally get the same stem?hashtag

Then both series will be considered โ€œsuitableโ€ for both variants. Keep the naming strict (different stems for different variants) to avoid overlap.

chevron-rightCan I use spaces/Unicode in file names?hashtag

You can, but itโ€™s better to avoid it. Shopify gives you a URL, and the script uses the base name from the URL. Recommendation: Latin letters, hyphens, and underscores.

chevron-rightWhat about videos and 3D models?hashtag

Filtering is applied only to media_type="image". Videos/models stay as in the theme. If you want them to be variant-specific too, you can extend the matching logic using data-media-type and base names of posters/URLs.

chevron-rightWhat happens if I rename a file later in Files?hashtag

Matching is based on the file name. If you rename the โ€œanchorโ€ or the series, either update the โ€œanchorโ€ name in the metafield or revert the previous name โ€” otherwise the series may be โ€œdetachedโ€ from the variant.

chevron-rightHow do I roll back?hashtag

Remove the JSON block, the server inserts vg_list/prehide, the unified common_data_attrs block, the script, and the mini CSS. After that the theme returns to its original behavior.

chevron-rightDoes this work across all languages/locales?hashtag

Yes. The logic does not depend on texts; only on attributes and file names.

chevron-rightIs it safe to update the theme?hashtag

Major theme updates can overwrite _product-media-gallery.liquid. Keep git/Theme Download and run a diff: if Shopify changes the structure of this template, re-apply our inserts to the new version.

chevron-rightCan I attach all files in the series to the metafield instead of just the โ€œanchorโ€?hashtag

You can, but thereโ€™s no need. The logic is built around stems so you donโ€™t have to โ€œpokeโ€ every file manually.

chevron-rightCan I remove the data-initial-variant-id attribute?hashtag

Yes. Itโ€™s optional and does not affect the filter.

chevron-rightWhy is there only one photo in the quick add modal?hashtag

Thatโ€™s by design: on mobile quick add you donโ€™t want to overload the screen โ€” we show the current variantโ€™s featured image. On the PDP the full series remains.

Last updated