Alt-text driven variant media gallery with tiered matching and full-screen lightbox
The product gallery that consumes the alt-text match tiers: it builds the main slider, the thumbnail rail and a full-screen lightbox out of server-side verdicts, then re-filters the same nodes in the browser when the shopper changes variant.
Shopify attaches at most one variant to an image, so a product sold as size by formula cannot show a full set per variant. This gallery classifies every media item against the selected variant in Liquid, builds the Swiper track, thumbnail rail and full-screen lightbox from the verdicts, and parks hidden slides off-DOM so a variant change re-filters without a refetch.
Fact Strip
- Client: LUS Brands — loveurcurls.com
- Surface: Product detail page
- Templates served: four product templates
- Complexity: High
- Attribution: Deploi-authored. The gallery, the lightbox and the video controls are ours. It runs in a theme built on Dawn 15.4.1 and deliberately reuses Dawn's
product-thumbnail.liquidandproduct-media.liquidunchanged, including their responsivesizesmath. - Status: Live, verified 2026-09-06
- Evidence: Five Liquid snippets read from the theme, plus client working-session records from April and May 2026
- Primary capability: Variant-aware content and media swapping
The Problem
Shopify attaches at most one variant to an image. For a catalog whose variants are a size axis crossed with a formula axis, that ceiling is the whole problem: a variant cannot own a set of images, only a single one. Stores work around it two ways, and both are bad. Either the same lifestyle and education imagery is uploaded again under every variant, which multiplies the media library by the variant count and makes every future swap a bulk edit; or the gallery shows everything to everyone, and a shopper looking at the 3oz scrolls past three shots of a bottle they are not buying.
The 2026 rebrand asked for the version the platform does not offer. Each variant shows its own hero image and its own set. Shared media — lifestyle, education, ingredient art — stays visible on every variant, uploaded once.
The Constraint
The association data had to come from something the content team can already edit in the Shopify admin, which meant image alt text: there is no per-variant media list in Liquid to read instead, and no new admin surface was on the table.
That decision has a second half that is easy to miss. Everything is computed in Liquid at render time, with no Storefront API call — and then computed again, identically, in JavaScript. The lightbox is assembled client-side and re-orders and re-filters slides after a variant change without going back to the server, so the same match rules have to exist on both sides of the paint. Two languages, one convention, nothing enforcing agreement between them.
The theme underneath is Dawn. Dawn has its own opinion about product media and updates the gallery itself on variant change. Any custom gallery has to tell Dawn's product-info.js to stand down, or the two fight over the same DOM.
What We Built
de_product_media_gallery.liquid — roughly 3,700 lines, the largest snippet in the theme — is the renderer. It calls the two classification snippets documented on the alt-text convention page for every media item, and everything below is built from the verdicts they return.
Server-rendered structure. The gallery builds the main Swiper track, the thumbnail rail and the slide order from the tiers, marks the slide whose alt contains main as that variant's hero, and emits a VariantAltKeys-<section> JSON island listing every variant's match key and visible-media count. That island is the contract between the Liquid pass and the JavaScript pass: the browser applies keys rather than re-deriving them. The gallery also advertises data-use-alt-variant-media, so Dawn's product-info.js stands down from its own media updating and leaves the slides alone.
The lightbox. A full-screen <dialog> holds a second pair of Swipers — a main track and a thumbnail track — with arrows, an overflow-aware thumb rail that switches between centered and scrollable depending on measured width, and slide and thumb "parks": off-DOM containers that hidden nodes are moved into rather than deleted. Parking matters because a variant change is a re-filter, not a re-render. Nodes that come back on the next variant are already built, already have their images decoded, and only need moving.
Video. Video slides get a hand-built control bar — play/pause, scrub, mute, elapsed time — that reveals on pointer or keyboard interaction and is wired through Dawn's deferred-media:loaded event, so a video only gains controls once its player actually exists. Video and 3D slides are forced to full width; 3D models keep the Shopify XR button and the model-viewer stylesheets.
Reuse. Slide and thumbnail rendering still goes through Dawn's product-thumbnail.liquid and product-media.liquid, unchanged. The custom code is the matching, the ordering and the chrome around them.
Why This Way
Alt text was the only per-image field the merchandising team could already edit, and it doubles as accessibility copy, so the convention added no new admin surface and no new field to keep in step with the images.
Classifying in Liquid is what keeps the first paint correct. The gallery in the initial HTML is the gallery for the selected variant, which matters for crawlers, for shoppers with JavaScript disabled or slow, and for anyone who sees the page before a script has run. The JavaScript mirror exists for one reason only: the lightbox re-orders nodes client-side, and it can't ask the server to do that for it.
You pay for that twice, in two languages. The same rules are implemented once in Liquid and once in JavaScript, and the two only stay equivalent because someone moves them together. We took that on knowingly rather than give up the server-rendered first paint, which was the more valuable half.
Why Not an App
Variant-image-grouping apps solve exactly this problem, and if you want the behavior and don't want the maintenance, buying one is a reasonable call. The usual mechanism is to hold the mapping in the app's database and rewrite the gallery client-side after load.
That shape has two consequences we didn't want here. The images in the server-rendered HTML are the wrong ones until the app's script runs, so your shopper can watch a re-shuffle on a page whose hero image is the reason they came. And the mapping lives outside Shopify, edited separately from the media it describes. Keeping the association in the alt field makes the label and the image one object: rename it once, with no second record to update.
Implementation Notes
- The alt convention is
<option> + <option> [-- free text], withmainmarking that variant's hero image. Matching is case- and whitespace-normalized and handles non-breaking spaces pasted from spreadsheets. - Alt matching only engages when at least one media/variant pair classifies as
perfect. Otherwise the gallery falls back to Dawn's ownattached_to_variant?behavior, so an unlabeled product still works. see_all_media_count > 5gates both the See all thumbnail tile and the entire lightbox<dialog>markup, so short galleries never ship the dialog's DOM at all.- Swiper is self-hosted as a theme asset (
swiper-custom.min.js, enqueued fromtheme.liquid) rather than loaded from a CDN, which keeps the slider on the store's own origin and out of a third party's release schedule. - Dawn's
product-thumbnail.liquidandproduct-media.liquidare reused unchanged for slide and thumbnail rendering, including their responsivesizesmath and zoom classes, so a theme update touches one less custom file. - Hidden slides and thumbs are moved into off-DOM park containers rather than removed, so re-showing them after a later variant change needs no re-render and no second fetch.
Edge Cases
- Images with no alt text are treated as shared and shown for every variant, which is the safe default while a catalog is only partly labeled.
- Media claimed perfectly by another variant is hidden rather than shown as a partial match, so a size-specific shot cannot leak into a sibling variant's gallery.
- Single-media and no-media products skip the slider chrome entirely through the
single_media_visibleandhide_mobile_sliderflags, so a one-image product does not render dead arrows and an empty thumbnail rail. - 3D models still get the Shopify XR button and the
model-viewerstylesheets; video and model slides are forced to full width regardless of the layout the images use. - The lightbox thumbnail rail switches between centered and scrollable depending on measured overflow, so a four-thumb product and a twenty-thumb product both look deliberate.
- A
VariantAltKeysJSON parse failure degrades to showing every media item rather than throwing, so the worst case is an unfiltered gallery instead of an empty one.
Platform Primitives Used
section-rendering-api— Dawn's variant change runs through it; the gallery opts out of the media half of that update withdata-use-alt-variant-mediaand re-filters the slides it already holds.custom-elements— the video control bar hangs off Dawn's deferred-media custom element, listening fordeferred-media:loadedrather than polling for a player.metafields— the product page around the gallery is assembled from product metafields. The gallery itself deliberately takes none: its inputs are the media library and its section settings.
Where It Runs
Product detail page only, mounted by four product templates: the default product template, the 2026 redesign template, the kids redesign template and the waiting-list template. The same snippet serves all four, so a change to the match behavior lands on every product page at once rather than being configured per template.
What This Demonstrates
- Variant-aware content and media swapping — the primary capability, and the largest single implementation of it in the corpus. Media, hero selection and slide order all resolve from the selected variant.
In the taxonomy this build is also indexed under PDP media gallery and zoom, and under bundle pricing, since the bundle templates share the gallery.
How We Know
Five Liquid snippets read directly from the theme, roughly 4,000 lines across them, plus client working-session records from April and May 2026. Three separate documented requirements meet in this one component: an aligned decision that product-page images must update with the selected variant, a requirement for an image zoom and magnifier on the PDP, and a later aligned decision to swap image positions on the product page to match the design.
The alt-text mechanism itself appears in none of them. The requirements are documented; the convention that satisfies them was a code-side decision, and this page reconstructs it from the snippets.
Related Builds
- Alt-text convention that maps product media to variants without metafields — the classifier underneath this gallery: the match key, the four tiers, the normalization that makes human alt text comparable. That page covers the matching rules; this one covers what gets built from them.
- Variant-aware PDP hero banner driven by a 'heroic' metaobject — the same product page, the same variant event, the opposite storage decision: a metaobject rather than a convention.
- Product card with award badges, per-variant content payload and per-card structured data — a different storefront carrying per-variant content into the grid. The cross-client comparison is the useful part.
The Buy-vs-Build Question
An app buys the behavior for a monthly fee and a client-side repaint. Building it bought a correct first paint and a mapping held in the store's own data, and cost a large custom snippet plus a JavaScript mirror of its rules. That trade goes the other way for plenty of catalogs. The test: Variant limits and complex options: buy or build?
Ready to Build a PDP That Keeps Up With Your Catalog?
You dream it. We build it. If your product page is working around Shopify's variant model instead of with it, Contact us today and we'll walk you through the options — the build, the app, and where the line sits.