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.
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.1.5 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 updated the gallery code 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
},
Use a color-filtered media list
Open snippets/product-media-gallery-content.liquid. Near the top, find the Liquid block containing
assign selected_product = closest.product.
Replace the statements from that assignment up to, but not including,
if block_settings.slideshow_controls_style == 'thumbnails' with the code below. Keep the existing opening
{%- liquid and closing -%} tags, along with all statements after the replacement. If those
boundaries do not match your file, do not guess at a replacement.
Code preview — expand below for the complete code to copy.
# Modified by CheckoutWorks.dev
assign selected_product = closest.product
assign selected_variant_media = selected_product.selected_or_first_available_variant.featured_media
assign first_3d_model = selected_product.media | where: 'media_type', 'model' | first
Collapse code
# Modified by CheckoutWorks.dev
assign selected_product = closest.product
assign selected_variant_media = selected_product.selected_or_first_available_variant.featured_media
assign first_3d_model = selected_product.media | where: 'media_type', 'model' | first
if block_settings.hide_variants
assign variant_images = selected_product.images | where: 'attached_to_variant?', true | map: 'src'
endif
assign color_media_grouping_active = false
assign selected_color_value = blank
assign color_option = blank
if block_settings.group_media_by_color_alt
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
assign selected_color_value = product_option.selected_value
break
endif
endfor
endif
assign media_pool = selected_product.media
if color_option != blank and selected_color_value != blank
assign grouped_media = '' | split: ','
assign selected_color_media_count = 0
assign normalized_selected_color = selected_color_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 color_media_grouping_active = true
assign media_pool = grouped_media
endif
endif
assign sorted_media = '' | split: ','
assign selected_variant_media_in_pool = '' | split: ','
if selected_variant_media
assign selected_variant_media_in_pool = media_pool | where: 'id', selected_variant_media.id
endif
if selected_variant_media_in_pool.size > 0
assign sorted_media = sorted_media | concat: selected_variant_media_in_pool
for media in media_pool
if color_media_grouping_active == false and block_settings.hide_variants and variant_images contains media.src and sorted_media.size > 0
continue
endif
if media.id != selected_variant_media.id
assign found_media = media_pool | where: 'id', media.id
assign sorted_media = sorted_media | concat: found_media
endif
endfor
else
assign sorted_media = media_pool
endif
We filter the media list 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.