Want to show multiple variant images on your Shopify Horizon product page without an app? We built this feature by grouping product photos by color using image alt text and custom theme code. Shoppers see the selected color’s photos, while shared images remain visible.
Try the demo below, then follow how we built it. We’ve included the code and setup steps so you can adapt the implementation to your own theme.
Try the live demo
Switch colors to explore matching image groups. Shared images stay visible across selections.
Color-based image groups on our Shopify Horizon demo.
Why a variant image is not enough
Shopify lets you assign one image to each variant. That changes the variant preview, but it does not assign a whole group of front views, model photos, and other angles to one color.
If even the single variant image does not switch when a shopper selects a variant, start with why a Shopify variant image is not changing. This guide assumes the standard variant image already works.
This customization filters the gallery by the selected Color option. It does not change Shopify’s variant image limit. Horizon’s Hide unselected variant media setting is different: it does not read color names from alt text to create these groups.
Before you start
- Duplicate your theme and work on the unpublished copy.
- Use a product with an option named Color or Colour, plus images for each color.
- Check for apps or custom code that already control the product gallery.
Code reference: the snippets below come from our local Horizon 4.3.0 implementation. Compare your files before editing; this is not a drop-in tutorial for every theme or version. “Without an app” still means making a code change.
1. Group images using descriptive alt text
For our demo, we used Black, Military Green, and Natural, with several photos per color and one shared image. The grouping reads the color names in the alt text, not the file names.
To use the same approach, upload your images to the product and describe each one using its actual color option name. For example, use Military Green when that is the option value, rather than shortening it to “Green.”
| Example alt text | Image group |
|---|---|
| Black T-shirt shown from the front | Black |
| Model wearing a Military Green T-shirt | Military Green |
| Natural T-shirt shown from the front | Natural |
| Three blank T-shirts layered in a studio product photo | Shared across colors |
A shared image must not contain any of the product’s color option names in its alt text. Keep the description meaningful: alt text still describes the image for people who cannot see it. Do not leave it blank just to make an image shared.
The first image stays shared; the other images include their color names. Select the screenshot to view it at full size.
Watch for ambiguous words. If one color is Natural, “natural lighting” also matches it. Overlapping names such as Black and Washed Black can match the same image. This approach suits clearly distinguishable color names; it is not a general-purpose tagging system.
2. How we built the gallery behavior
We added a setting to Horizon’s product media block, then added one block of code to the gallery snippet to group images by color. The implementation uses Horizon’s existing variant-selection behavior, so it does not need a separate JavaScript layer.
The two changes below come from that implementation. Your files may differ if you use another theme version or have existing customizations. Keep a copy of the original files and work in your duplicate theme.
You can adapt the changes yourself or use an AI coding assistant to compare your files with our example. Provide both code examples below and the relevant theme files. Ask it to preserve existing customizations, explain where each change belongs, and flag any differences that need a different approach. Do not include passwords, access tokens, or customer data.
Review every change before applying it. AI-generated adjustments still need testing on an unpublished theme copy; they are not a guarantee of compatibility.
Add the Variant image groups checkbox
Open blocks/_product-media-gallery.liquid. In its settings array, find the object with
"id": "hide_variants". Insert the following object after that object and before the next
setting. Keep the comma after the existing object and the trailing comma shown below.
{
"type": "checkbox",
"id": "group_media_by_color_alt",
"label": "Variant image groups",
"info": "Match images by Color using ALT text. Images without a Color stay shared.",
"default": false
},
Add the color grouping code
Open snippets/product-media-gallery-content.liquid. In the Liquid block near the top, find this line:
assign has_image_drop = sorted_media | has: 'media_type', 'image'
Paste the code below on a new line directly above it. Do not remove or change any existing line. If your file does not contain that line, do not guess at another position.
Code preview — expand below for the complete code to copy.
# Modified by CheckoutWorks.dev: optional Color grouping by image ALT text.
# Runs after the upstream sorting above and replaces its result only when the
# selected Color matches at least one image.
if block_settings.group_media_by_color_alt
assign color_option = blank
for product_option in selected_product.options_with_values
assign normalized_option_name = product_option.name | strip | downcase
if normalized_option_name == 'color' or normalized_option_name == 'colour'
assign color_option = product_option
…
Collapse code
# Modified by CheckoutWorks.dev: optional Color grouping by image ALT text.
# Runs after the upstream sorting above and replaces its result only when the
# selected Color matches at least one image.
if block_settings.group_media_by_color_alt
assign color_option = blank
for product_option in selected_product.options_with_values
assign normalized_option_name = product_option.name | strip | downcase
if normalized_option_name == 'color' or normalized_option_name == 'colour'
assign color_option = product_option
break
endif
endfor
if color_option != blank and color_option.selected_value != blank
assign grouped_media = '' | split: ','
assign selected_color_media_count = 0
assign normalized_selected_color = color_option.selected_value | strip | downcase | replace: '-', ' ' | replace: '_', ' '
for media in selected_product.media
assign normalized_media_alt = media.alt | default: '' | strip | downcase
assign normalized_media_alt = normalized_media_alt | replace: '-', ' ' | replace: '_', ' ' | replace: ',', ' ' | replace: '.', ' ' | replace: ':', ' ' | replace: ';', ' ' | replace: '/', ' '
assign searchable_media_alt = ' ' | append: normalized_media_alt | append: ' '
assign media_matches_any_color = false
assign media_matches_selected_color = false
for color_value in color_option.values
assign normalized_color_value = color_value.name | default: color_value | strip | downcase | replace: '-', ' ' | replace: '_', ' '
assign color_value_needle = ' ' | append: normalized_color_value | append: ' '
if searchable_media_alt contains color_value_needle
assign media_matches_any_color = true
if normalized_color_value == normalized_selected_color
assign media_matches_selected_color = true
endif
endif
endfor
if media_matches_selected_color
assign selected_color_media_count = selected_color_media_count | plus: 1
endif
if media_matches_selected_color or media_matches_any_color == false
assign found_media = selected_product.media | where: 'id', media.id
assign grouped_media = grouped_media | concat: found_media
endif
endfor
if selected_color_media_count > 0
assign sorted_media = '' | split: ','
if selected_variant_media
assign sorted_media = grouped_media | where: 'id', selected_variant_media.id
endif
if sorted_media.size > 0
for media in grouped_media
if media.id != selected_variant_media.id
assign found_media = grouped_media | where: 'id', media.id
assign sorted_media = sorted_media | concat: found_media
endif
endfor
else
assign sorted_media = grouped_media
endif
# The upstream values above are based on the full media count; redo them for the group.
assign slideshow_controls_style = block_settings.slideshow_controls_style
if slideshow_controls_style == 'dots' and sorted_media.size > 15
assign slideshow_controls_style = 'counter'
endif
if sorted_media.size <= 1
assign slideshow_class = 'product-media-gallery__slideshow--single-media slideshow--single-media'
assign render_slideshow_arrows = false
endif
endif
endif
endif
The code runs after Horizon’s own media sorting and replaces the result only when the selected color matches at least one image. Every original line stays in place, which keeps the change easy to find and reapply after a theme update.
The media list is filtered before Horizon renders the gallery, rather than hiding finished slides with CSS. The gallery, thumbnails, counter, and zoom view can then use the same list. Switching Size does not choose another color group, although a variant’s assigned image can still affect which image appears first.
3. Enable the feature
Preview the product in the theme editor, open its Product media block, and enable Variant image groups. This checkbox appears only after adding the code above.
Variant image groups is the custom setting. The native setting above it does not need to be enabled for color grouping to work.
The switch belongs to the media block on the selected template, not to one product record. If only certain products should use it, assign them a dedicated template.
4. Test before publishing
- Switch colors, then sizes. Check the matching images and shared media.
- Test mobile swiping, desktop layouts, thumbnails, counters, and zoom.
- Open a direct variant URL and try unavailable combinations and quick color changes.
- Disable the setting and confirm the product returns to the original gallery behavior.
This guide focuses on the main product image gallery. Video, 3D media, Quick add, Featured product sections, and Combined Listings need separate checks before you rely on them. Review and retest the customization after a theme update.
If the images do not group as expected
- All colors appear: check the switch, the Color/Colour option name, and the alt text. If no image matches the selected color, the code falls back to Horizon’s native gallery logic.
- An image appears everywhere: it may not match any color name, so it is treated as shared. Check spelling and multiword color names.
- Colors overlap: check for overlapping names or descriptive words such as “natural.” Changing alt text can change grouping.
For a working reference, open the live Horizon demo and compare its color groups with your preview. Keep the duplicate unpublished until the gallery behaves correctly for your products.