help1
horizon sale badgeshopifyshopify theme customizationhorizon theme

Chasing the Horizon Sale Badge Across Five Stores

A five-store chase after the missing horizon sale badge: which file to edit, why your custom block lies on variant switch, and what still has no fix.

help1 Team
Chasing the Horizon Sale Badge Across Five Stores

Open your Horizon store on a desktop browser, go to a product page for a product with a compare_at_price set, and watch where the "Sale" label does and does not render. In half the audits I did this month the label showed on the collection card and vanished on the product page. In the other half it showed on the product page and lied on the collection card: a correct 20% off on the first size, the exact same 20% off on all four sizes, even though two of them were full price. Those two symptoms are the fingerprint. The chase below is how I actually ran it.

I pulled five stores from this month's help1 queue that reported some version of this. Four had migrated from Dawn in the last ninety days. One started on Horizon fresh. All five were on the current Horizon release at the time I looked. What follows is the walk, in roughly the order I checked things, with the wrong hypotheses included because the wrong hypotheses are the point.

What I checked first

The reflex, if you have ever worked on Dawn, is to open snippets/price.liquid. That is where Dawn put the badge logic. Dawn merchants know that file. The percentage calculation sits in there as a comment-rich block anyone can paste into: {%- assign percent = product.compare_at_price | minus: product.price | times: 100 | divided_by: product.compare_at_price | round -%}. Edit, save, done.

On Horizon, snippets/price.liquid still exists. Edit it, save it, and look at your product page. The badge updates on the product page. Then click into a collection, and the collection card is still a bare "Sale" label on products where the merchant wanted "25% OFF", or on three of the five stores it was missing entirely. The reflex fix only moved half the symptom.

That was the first dead end. I had edited the right file for the product page and the wrong file for the collection card, because Horizon has two files.

Why that wasn't it

Horizon separates the horizon sale badge code from the product page price code. The product page uses snippets/price.liquid. The collection card uses a different snippet entirely. On older Horizon versions it is snippets/product-card-badges.liquid. On current Horizon versions it moved to blocks/_product-card-gallery.liquid. The AjayCodeWiz guide at getpinflow.com (July 2026) is the first source I know of that names the new file explicitly. The Shopify theme changelog does not mention the move. Neither file name is wrong; they are both the correct name on different versions of the same theme.

The practical consequence of this split is that every "add a percentage badge to Horizon" guide you find on Google is right about half of the fix and silent about the other half. The merchants in my queue had each done the half the guide covered. The site looked wrong in a different place than the one they had edited, and they assumed the edit "didn't take."

The way out of this trap is not to trust the file name in any guide. Open the code editor, pop open your browser's find-in-page (Ctrl+F works), and search for the string product_badge_sale. The file that contains {{ 'content.product_badge_sale' | t }} is your badge file for the current version of your theme. On older Horizon it will be in snippets/. On current Horizon it will be in blocks/. Either way, that is the line to replace with your percentage calculation. The string works across versions and does not care which path Shopify picked this release.

I spent twenty minutes on store three convinced the code editor had cached a stale file tree, because the badge snippet "wasn't there." It was. It was in blocks/. I had been looking in snippets/ because that is what the merchant's screenshot showed, and the merchant's screenshot was from a tutorial published fourteen months ago.

The clue in the custom block that kept lying

Three of the five stores had already tried the "safe" fix. The safe fix, the one every community expert recommends to avoid losing work on theme updates, is a Custom Liquid block. You add it from the theme customizer, you paste a few lines of Liquid, you never touch core theme files, your work survives theme updates. tim_tairli, the community member I trust most on Horizon customization, documents this pattern across multiple threads from 2025 through April 2026, and it is the right default advice for almost every piece of card customization in Horizon.

For a horizon sale badge, the Custom Liquid block almost works. On page load, it shows the right number. Click a different variant, and the number does not change.

The reason took me longer to find than it should have. Horizon's product page price lives inside a reactive custom element called <product-price>. When a customer picks a different size or color, that element re-renders its own contents from the newly selected variant's prices. Anything that sits inside the <product-price> wrapper updates. Anything that sits outside it, including a Custom Liquid block positioned above or below the price, is static HTML from the first render. The badge in that block will show the first variant's math for the entire session.

v.marychenka wrote the cleanest one-line summary of this on the Shopify Community thread for the September 2026 badge question: Horizon replaces only what sits inside the price container; a badge in a Custom Liquid block sits outside it, showing the first size's numbers. shopplaza_team confirmed the mirror of that in the same thread: because <product-price> is a reactive custom element that re-renders its contents on variant change, a badge calculation placed inside it auto-updates with no JavaScript at all.

The uncomfortable implication is that for the product page, there is no path that is both update-safe and reactive. You either edit snippets/price.liquid directly (reactive, but the edit may need re-applying on a major theme update) or you keep the Custom Liquid block (survives updates, shows the wrong number). On the collection cards you can keep the block because collection cards do not re-render on variant selection anyway, which is why the "safe path" works there and only there.

The multi-variant trap I almost shipped

On the collection card side, I almost shipped the standard shortcut. Every community guide from 2024 and 2025 uses the same percentage math: product.variants.first.compare_at_price and product.variants.first.price. That reads the first variant's prices and computes the discount against them. For a store where every variant shares the same sale, this works fine. The result is the same for every variant anyway.

On store four it was quietly wrong. Store four sells one SKU in four sizes. Three sizes are discounted. The first-listed size, which is also the one Shopify shows first in the admin, is full price. The badge rendered "0% OFF" on the collection card. The merchant had, by chance, built a product where the shortcut lies.

The February 2026 WebSensePro guide walks through the fix: loop over all variants, compute the discount for each, and take the maximum. Display that figure. Community threads from mid-2026 have converged on the loop approach as the standard, but older guides still show the shortcut, and the shortcut is still what most merchants paste from a chat window. If your products have per-variant pricing where some variants are on sale and others are not, you need the loop. Otherwise either approach will produce the same answer and you can skip the extra lines.

I will say the honest part: on three of the five stores, every variant was discounted equally, and the loop and the shortcut produced identical output. The shortcut would have shipped and the merchant would have been happy. The loop is the correct answer; whether you need it depends on whether your catalog has mixed-discount variants, and most solo-merchant catalogs on Horizon do not.

What I actually shipped

Here is the full fix I ran across the five stores, written as a sequence rather than a theory.

  1. Find your badge file. Open the code editor and search for product_badge_sale. If it hits snippets/product-card-badges.liquid, you are on an older Horizon. If it hits blocks/_product-card-gallery.liquid, you are on a current one. Edit the file that contains the hit.
  2. Replace {{ 'content.product_badge_sale' | t }} with a percentage calculation. For catalogs where every variant shares the same sale, use the shortcut: {%- assign percent = product.compare_at_price | minus: product.price | times: 100 | divided_by: product.compare_at_price | round -%}{{ percent }}% OFF. For mixed-discount catalogs, loop product.variants, compute the discount for each, take the max.
  3. For the product page horizon sale badge, open snippets/price.liquid. Place the same calculation inside the <product-price> wrapper, not above or below it. Only code inside that element re-renders on variant change. Back the theme up before the edit; this one will need re-applying if Shopify ships a non-trivial Horizon update.
  4. For the "from" price on collection cards, add a Custom Liquid block via the theme customizer. Inside the block, use closest.product, not product. A plain product reference in a collection card returns the page-scope product and every card renders the same data. closest.product walks up to the card's actual product. Check closest.product.price_min != closest.product.price_max and output "From " plus the min price when they differ.
  5. If positioning or sizing is off (badge too large, wrong corner), do not edit base.css or theme.css. Multiple merchants have reported those edits silently fail. Paste the CSS into the custom CSS field in the theme customizer. The selector you want is .product-badges.product-badges--top-right. ads18922 documented this exact pattern on the Shopify Community in September 2025.

If you are halfway through step three and your edit is not taking, step one is where to go back to. If you are stuck between steps and want a second pair of eyes before you push a theme change, the first fifteen minutes with a help1 expert is free and that is usually enough time to confirm which file to touch.

The other thing worth saying up front, especially if you are a Dawn migrator: this is part of a wider pattern. Dawn and Horizon share a surface vocabulary and almost no file structure. I wrote a longer piece on five customization habits that break on Horizon for merchants in the first weeks post-migration; the badge story in this post is one case of the same shape.

The one I could not fix

Horizon's recommended products section has a bug I did not solve on any of the five stores. Tooba_Amin posted the clearest diagnosis of it on the Shopify Developer Community in November 2025: inside the recommended products block, the inner Price Block looks up a product variable, misses the card's local context, and falls back to the global scope. The result is that every recommendation card shows the price of the current product page, not its own price.

As of this writing I have not seen a confirmed self-serve fix on that thread or elsewhere. The workaround I offered on store two was to replace the native recommended products block with a third-party app that manages its own card rendering, which costs the merchant another app subscription and is not a real answer. A proper patch would pass the recommended product explicitly to the nested Price Block through a render tag with a product: argument, which is theme-code work and not a one-liner. If you are on Horizon and your recommended products section is showing the wrong price, that is the known cause, and I do not currently have a clean fix I can recommend without a developer in the loop.

I keep thinking the horizon sale badge story is really about this: badges are a thing merchants assume theme vendors have thought about, and in Horizon the architecture has been pulled apart into pieces that nobody has drawn a map of yet. Every fix in this post comes from a merchant hitting a wall, posting about it, and someone with the file path writing it down. The reason this post exists at all is that the walls keep getting hit, and the file paths keep moving.

Still stuck? Talk to an expert.

Our vetted Shopify experts can fix this issue for you in a live session. $39 per session. Your first 15 minutes are free.