Shopify Horizon: How to Show Multiple Images Per Color Without an App

·Johnny Chen Founder & Shopify Developer at CheckoutWorks
Shopify Horizon product galleries comparing all images with color-specific image groups when Black is selected.

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.

Explore our live demo →

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.

Shopify Files showing Natural and Military Green image alt text, plus a shared T-shirt image without a color name.

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
Show full codeCollapse 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.

Custom Variant image groups toggle below Hide unselected variant media in the Shopify Horizon theme editor.

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.

HAVE A SHOPIFY PROBLEM?

LET’S
UNTANGLE IT _

────

You do not need a perfect technical brief.

Describe what's broken in your own words. Fast 24-hour turnaround, backed by a 100% full refund guarantee if we can't fix it.

We like hard problems. You shouldn’t have to.