How to Show Discount Percentage Badge on Shopify

๐ŸŽ“ Free YouTube Course

Learn WordPress Theme Development step by step

Watch on YouTube

How to Show Discount Percentage Badge on Shopify

To show a percentage discount badge on Shopify, edit your theme’s card-product.liquid and price.liquid files. Calculate the discount with Liquid โ€” compare-at price minus price, divided by compare-at price, times 100 โ€” and replace the default “Sale” badge text with the result, like “-25%”. No app required.

 The Code to find : price.liquid

products.product.on_sale

The Code to replace it with :

-{{ compare_at_price | minus: price | times: 100 | divided_by: compare_at_price }}% OFF

The second Code to find : card-product.liquid

products.product.on_sale

The Code to replace it with :

-{{ card_product.compare_at_price | minus: card_product.price | times: 100 | divided_by: card_product.compare_at_price }}% OFF
.card__badge .badge,
.price .price__badge-sale,
.product .badge,
.product-information .price__badge-sale {
  border-radius: 1.3rem;
  background: linear-gradient(120deg,  /* The values below this line are the badge colors */
    #ffd1d1 0%, 
    #fd8585 35%,
    #ee0979 100%);
  font-weight: 700;
  border: none;
  color: white;
  position: relative;
  overflow: hidden;
  transition: all 0.3s ease;
  box-shadow: 0 2px 5px rgba(0,0,0,0.1);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-family: var(--font-body-family);
  transform-origin: left center;
  margin: 0;
  display: inline-block;
  line-height: 1.1;
}

.card__badge .badge,
.price .price__badge-sale {
  padding: 0.5rem 1.3rem !important; 
  font-size: 1rem !important;
  transform: scale(1.3) translateY(15%) !important;
}

.product .badge,
.product-information .price__badge-sale {
  padding: 0.45rem 1.3rem !important; 
  font-size: 0.95rem !important;
  transform: scale(1.5) translateY(15%) !important;
  margin-bottom: 0.85rem;
  transform-origin: left top;
}

.card__badge .badge:hover,
.price .price__badge-sale:hover {
  transform: scale(1.3) translateY(calc(15% - 2px)) !important;
  box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}

.product .badge:hover,
.product-information .price__badge-sale:hover {
  transform: scale(1.5) translateY(calc(15% - 2px)) !important;
  box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}

.card__badge .badge::after,
.price .price__badge-sale::after,
.product .badge::after,
.product-information .price__badge-sale::after {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(
    90deg, 
    rgba(255,255,255,0) 0%, 
    rgba(255,255,255,0.3) 50%, 
    rgba(255,255,255,0) 100%
  );
  transform: skewX(-20deg);
  transition: all 0.75s ease;
}

.card__badge .badge:hover::after,
.price .price__badge-sale:hover::after,
.product .badge:hover::after,
.product-information .price__badge-sale:hover::after {
  left: 100%;
}

.card__badge,
.product .badge-container {
  overflow: visible !important;
}

.price .price__badge-sale {
  margin-top: 0 !important;
  margin-bottom: 0 !important;
}

.product-information .price__badge-sale {
  position: relative;
}

.card__information {
  margin-top: 0.85rem;
}

.price--on-sale .price__container {
  position: relative;
}

@media screen and (max-width: 749px) {
  .card__badge .badge,
  .price .price__badge-sale {
    padding: 0.45rem 1.1rem !important;
    font-size: 0.9rem !important;
  }
  
  .product .badge,
  .product-information .price__badge-sale {
    padding: 0.4rem 1.1rem !important;
    font-size: 0.9rem !important;
  }
}

Without position adjustments :

.card__badge .badge,
.price .price__badge-sale,
.product .badge,
.product-information .price__badge-sale {
  border-radius: 1.3rem;
  background: linear-gradient(120deg,  /* The values below this line are the badge colors */
    #ffd1d1 0%, 
    #fd8585 35%,
    #ee0979 100%);
  font-weight: 700;
  border: none;
  color: white;
  position: relative;
  overflow: hidden;
  transition: all 0.3s ease;
  box-shadow: 0 2px 5px rgba(0,0,0,0.1);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-family: var(--font-body-family);
  margin: 0;
  display: inline-block;
  line-height: 1.1;
  margin-left: 10px !important;
  margin-right: 10px !important;
}

.card__badge .badge,
.price .price__badge-sale {
  padding: 0.5rem 1.3rem !important;
  font-size: 1rem !important;
  transform: scale(1.3) !important;
}

.product .badge,
.product-information .price__badge-sale {
  padding: 0.45rem 1.3rem !important;
  font-size: 0.95rem !important;
  transform: scale(1.5) !important;
}

.card__badge .badge:hover,
.price .price__badge-sale:hover {
  transform: scale(1.3) !important;
  box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}

.product .badge:hover,
.product-information .price__badge-sale:hover {
  transform: scale(1.5) !important;
  box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}

.card__badge .badge::after,
.price .price__badge-sale::after,
.product .badge::after,
.product-information .price__badge-sale::after {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(
    90deg, 
    rgba(255,255,255,0) 0%, 
    rgba(255,255,255,0.3) 50%, 
    rgba(255,255,255,0) 100%
  );
  transform: skewX(-20deg);
  transition: all 0.75s ease;
}

.card__badge .badge:hover::after,
.price .price__badge-sale:hover::after,
.product .badge:hover::after,
.product-information .price__badge-sale:hover::after {
  left: 100%;
}

.card__badge,
.product .badge-container {
  overflow: visible !important;
}

.price .price__badge-sale {
  margin-top: 0 !important;
  margin-bottom: 0 !important;
}

.price--on-sale .price__container {
  position: relative;
}

@media screen and (max-width: 749px) {
  .card__badge .badge,
  .price .price__badge-sale {
    padding: 0.45rem 1.1rem !important;
    font-size: 0.9rem !important;
  }
  
  .product .badge,
  .product-information .price__badge-sale {
    padding: 0.4rem 1.1rem !important;
    font-size: 0.9rem !important;
  }
}

Walk through any major online store during a sale and look at the product grids. You won’t see plain “Sale” labels. You’ll see numbers: -30%, -50%, Save 25%.

There’s a reason for that. “Sale” tells the shopper something is discounted. “-40%” tells them how much โ€” and it’s the size of the number, not the existence of the discount, that triggers the click. A generic sale badge makes shoppers do math or open the product page to find out if the deal is worth it. A percentage badge does the selling right there in the collection grid.

Now here’s the Shopify reality: when you set a compare-at price on a product, themes like Dawn automatically display a badge โ€” but it just says “Sale.” No percentage, no amount, no setting anywhere in the theme editor to change that. The number you want is one small calculation away, but Shopify doesn’t do it for you.

The good news: this is one of the easiest, highest-impact code customizations in all of Shopify. In this tutorial, I’ll show you exactly how to replace the plain Sale badge with a dynamic percentage badge โ€” like “-25%” โ€” that calculates itself automatically from your prices, updates when you change a discount, and works in both places badges appear:

  1. Collection pages and product cards (the grid)
  2. The product page price area

I’ll also show you the “Save $X” amount variation, how to style the badge, and how to handle the tricky edge cases (variants with different discounts, rounding, hiding tiny discounts). All free, no app, about 20 minutes. Let’s do it.


Table of Contents

  • Why a Percentage Badge Outsells a Plain “Sale” Badge
  • How Shopify Sale Badges Actually Work (Compare-At Price)
  • Before You Start
  • Step 1: Set Compare-At Prices Correctly
  • Step 2: Show the Percentage on Collection Cards
  • Step 3: Show the Percentage on the Product Page
  • Step 4: Style Your Discount Badge
  • Variation: “Save $X” Amount Instead of Percentage
  • Bonus Upgrades
    • Round to the Nearest 5%
    • Hide the Badge for Small Discounts
    • Handle Variants with Different Discounts
  • Best Practices for Discount Badges
  • Common Mistakes to Avoid
  • Troubleshooting
  • FAQ
  • Conclusion

Why a Percentage Badge Outsells a Plain “Sale” Badge

Discount psychology is well studied, and the findings are consistent:

  • Specific numbers beat vague claims. “-40%” is concrete and verifiable; “Sale” is noise that every store displays. Specificity reads as a real deal.
  • The grid is where comparison happens. Shoppers scan collection pages deciding which products to even click. A visible percentage makes discounted items pop against full-price neighbors โ€” exactly the moment badge information matters most.
  • Bigger numbers, stronger pull. For products under roughly $100, percentages usually look bigger than amounts (“25% off” a $40 item sounds better than “$10 off”). For expensive items, the dollar amount often wins (“Save $200” on a $1,000 item beats “20%”). This is sometimes called the Rule of 100 โ€” and it’s why I’ll show you both versions.

One honest caveat: badges amplify real discounts; they can’t manufacture demand for weak ones. A “-5%” badge can actually hurt, which is why one of the bonus upgrades below hides badges under a threshold you choose.

How Shopify Sale Badges Actually Work (Compare-At Price)

No app or hidden setting controls the sale badge. The logic is simple and built into your theme:

If a product’s compare-at price is higher than its price, the theme shows a sale badge.

The price is what customers pay. The compare-at price is the “was” price, displayed with a strikethrough. Both live on the product (or variant) in your Shopify admin.

Your theme checks this condition in two files:

  • card-product.liquid (Snippets) โ€” renders the badge on collection grids, search results, and featured-product sections
  • price.liquid (Snippets) โ€” renders the badge in the product page’s price area

Both currently output a translated “Sale” string. Our job: replace that string with a live calculation. The formula, in plain terms:

(compare-at price โˆ’ price) รท compare-at price ร— 100 = discount %

Example: compare-at $80, price $60 โ†’ (80 โˆ’ 60) รท 80 ร— 100 = 25%.

Before You Start

You’ll need a store on Dawn or a Dawn-based theme (Sense, Craft, Refresh, Studio โ€” file names and code match closely; other OS 2.0 themes use the same concept with different file names), and about 20 minutes.

And the rule that never changes: duplicate your theme. Go to Online Store โ†’ Themes โ†’ โ‹ฏ โ†’ Duplicate, make every edit on the copy, and publish only when it works.

Step 1: Set Compare-At Prices Correctly

The badge can only calculate from real data. For every product you want badged:

  1. Go to Products and open the product
  2. In the Pricing section, set Price to the discounted price customers pay
  3. Set Compare-at price to the original price
  4. If the product has variants, do this per variant (or use the bulk editor: select variants โ†’ edit compare-at price)
  5. Click Save

Quick sanity check: compare-at must be higher than price, or no badge appears anywhere.

Step 2: Show the Percentage on Collection Cards

This is the high-impact one โ€” the grid badge.

  1. Go to Online Store โ†’ Themes โ†’ โ‹ฏ โ†’ Edit code on your duplicate
  2. Open Snippets โ†’ card-product.liquid
  3. Search (Ctrl/Cmd + F) for on_sale โ€” you’ll land on the badge markup, which looks similar to this:

liquid

{%- if card_product.compare_at_price > card_product.price and card_product.available -%}
  <span
    id="Badge-{{ section_id }}-{{ card_product.id }}"
    class="badge badge--bottom-left color-{{ settings.sale_badge_color_scheme }}"
  >{{- 'products.product.on_sale' | t -}}</span>
{%- endif -%}
  1. Replace the translated text โ€” the {{- 'products.product.on_sale' | t -}} part โ€” with a calculation, so the block becomes:

liquid

{%- if card_product.compare_at_price > card_product.price and card_product.available -%}
  {%- assign discount_pct = card_product.compare_at_price
        | minus: card_product.price
        | times: 100.0
        | divided_by: card_product.compare_at_price
        | round -%}
  <span
    id="Badge-{{ section_id }}-{{ card_product.id }}"
    class="badge badge--bottom-left color-{{ settings.sale_badge_color_scheme }}"
  >-{{ discount_pct }}%</span>
{%- endif -%}
  1. Click Save

Two details that matter in that snippet:

  • times: 100.0 (with the decimal) forces floating-point math. Without it, Liquid does integer division and many discounts round to the wrong number.
  • round turns 24.7 into 25 โ€” clean badges, no decimals.

Preview a collection with discounted products: every sale badge in the grid now reads -25%, -40%, whatever your prices produce โ€” calculated live, per product.

Step 3: Show the Percentage on the Product Page

Same idea, second location.

  1. Open Snippets โ†’ price.liquid
  2. Search for on_sale โ€” you’ll find the product page’s badge, similar to:

liquid

<span class="badge price__badge-sale color-{{ settings.sale_badge_color_scheme }}">
  {{ 'products.product.on_sale' | t }}
</span>
  1. Replace it with:

liquid

{%- assign price_compare = compare_at_price | default: target.compare_at_price -%}
{%- assign price_actual = price | default: target.price -%}
{%- if price_compare > price_actual -%}
  {%- assign discount_pct = price_compare
        | minus: price_actual
        | times: 100.0
        | divided_by: price_compare
        | round -%}
  <span class="badge price__badge-sale color-{{ settings.sale_badge_color_scheme }}">
    -{{ discount_pct }}%
  </span>
{%- endif -%}
  1. Click Save

Note on Dawn versions: price.liquid internals vary a bit between releases โ€” some use a target object (the product or selected variant), some pass price/compare_at_price variables directly. The snippet above covers both patterns; if your file differs, the principle is unchanged โ€” find the sale badge span and swap the translated text for the calculation using whatever price variables the surrounding code already uses.

Bonus behavior you get for free: Dawn re-renders the price area when a shopper switches variants, so if variants carry different discounts, the badge updates automatically to match the selected variant. No JavaScript needed.

Step 4: Style Your Discount Badge

First, use the built-in setting: in Customize โ†’ Theme settings โ†’ Badges, Dawn lets you pick the sale badge’s color scheme and corner position with no code.

For more control, add this to the bottom of Assets โ†’ base.css:

css

/* ===== Percentage Discount Badge ===== */
.badge--bottom-left,
.price__badge-sale {
  font-weight: 700;
  letter-spacing: 0.05em;
}

/* Punchier look: red pill badge */
.card__badge .badge,
.price__badge-sale {
  background-color: #e53935;
  color: #ffffff;
  border: none;
  border-radius: 999px;
  padding: 0.5rem 1rem;
}

Adjust the red (#e53935) to your brand’s sale color. Red converts well for discounts almost universally, but consistency with your brand matters more than any single “best” color.

Variation: “Save $X” Amount Instead of Percentage

For high-ticket products, the dollar amount is often more persuasive. Same locations, different calculation:

liquid

{%- assign discount_amount = card_product.compare_at_price | minus: card_product.price -%}
<span class="badge badge--bottom-left color-{{ settings.sale_badge_color_scheme }}">
  Save {{ discount_amount | money }}
</span>

The money filter formats the amount in your store’s currency automatically. A popular hybrid used by many stores: percentage badges on collection grids (compact) and “Save $X” on the product page (concrete).

Bonus Upgrades

Round to the Nearest 5%

Odd numbers like “-23%” look accidental; marketing numbers end in 0 or 5. Swap the rounding line:

liquid

{%- assign discount_pct = card_product.compare_at_price
      | minus: card_product.price
      | times: 100.0
      | divided_by: card_product.compare_at_price
      | divided_by: 5 | round | times: 5 -%}

This rounds to the nearest 5 โ€” 23% displays as 25%, 41% as 40%. One caution: rounding up can overstate the discount (23% shown as 25%), which is a problem under advertising rules in many countries. If in doubt, round down instead by replacing round with floor in that chain โ€” accurate-or-understated is always safe.

Hide the Badge for Small Discounts

A “-3%” badge does more harm than good. Add a threshold:

liquid

{%- if discount_pct >= 10 -%}
  <span class="badge ...">-{{ discount_pct }}%</span>
{%- endif -%}

Now badges only appear from 10% off upward โ€” tune the number to taste.

Handle Variants with Different Discounts

On collection cards, card_product.price and card_product.compare_at_price reflect the product’s lowest-priced variant, so if variants have different discounts the grid badge shows one representative number. Two honest options:

  • Accept it (most stores do โ€” the product page badge shows the exact per-variant number once a variant is selected), or
  • Show “Up to -X%” by calculating from the variant with the deepest discount:

liquid

{%- assign max_pct = 0 -%}
{%- for variant in card_product.variants -%}
  {%- if variant.compare_at_price > variant.price -%}
    {%- assign pct = variant.compare_at_price | minus: variant.price | times: 100.0 | divided_by: variant.compare_at_price | round -%}
    {%- if pct > max_pct -%}{%- assign max_pct = pct -%}{%- endif -%}
  {%- endif -%}
{%- endfor -%}
{%- if max_pct > 0 -%}
  <span class="badge badge--bottom-left color-{{ settings.sale_badge_color_scheme }}">Up to -{{ max_pct }}%</span>
{%- endif -%}

“Up to” phrasing keeps you accurate when discounts vary within one product.

Best Practices for Discount Badges

Keep compare-at prices honest. The badge is only as truthful as the “was” price behind it. Inflated compare-at prices to fake big percentages violate consumer protection laws in many countries โ€” and shoppers smell it anyway.

Percentage under ~$100, amount above. The Rule of 100 is a solid default: “25% off” a $40 tee, “Save $150” on a $700 chair.

Badge the grid, detail the page. The grid badge’s job is to win the click; the product page can then show badge + strikethrough price + savings line together.

Don’t badge everything. If 100% of your catalog wears a discount badge year-round, the badge means nothing. Scarcity of badges is part of their power.

Match badge color to urgency, sitewide. One consistent sale color (usually red or your accent) across grid, product page, and any sale banners โ€” visual consistency compounds trust.

Re-check after theme updates. Updates replace card-product.liquid and price.liquid. Keep your edits saved; re-applying takes five minutes.

Common Mistakes to Avoid

  1. Forgetting 100.0 instead of 100. Integer math silently produces wrong percentages. The decimal point is load-bearing.
  2. Setting compare-at price equal to price. No badge appears โ€” compare-at must be strictly higher.
  3. Editing only one file. Change card-product.liquid but not price.liquid and your grid says “-25%” while the product page still says “Sale.” Do both.
  4. Editing the live theme. A Liquid typo here breaks every product card on the site at once. Duplicate first.
  5. Fake “was” prices. Beyond ethics and law, savvy shoppers use price-history tools; getting caught inflating compare-at prices costs far more than any badge gains.
  6. Translation surprise on multilingual stores. The default badge came from the translation system (on_sale); our replacement is hardcoded. If you run multiple languages, either keep the format language-neutral (“-25%” works everywhere) or reintroduce translated wording around the number.

Troubleshooting

No badge appears at all. Compare-at price is missing, equal to, or lower than the price โ€” check the product’s Pricing section (per variant, if applicable). Also confirm the product is in stock if your badge condition includes available.

The badge still says “Sale.” You edited the duplicate but are previewing the live theme (or vice versa), or your theme renders badges in a different file. Search all files (the code editor searches per file, so check card-product.liquid, price.liquid, and for some themes card.liquid or product-card.liquid) for on_sale.

The percentage is wrong (e.g., shows 0% or 100%). Almost always the integer-math bug โ€” confirm times: 100.0 includes the .0. Also check the order of filters matches the snippet exactly.

The badge shows on the grid but not the product page. Your Dawn version’s price.liquid uses different variable names. Open the file, find the existing sale badge span, and reuse whatever price/compare-at variables appear in the surrounding lines within our calculation.

Grid badge and product page badge show different numbers. Expected when variants carry different discounts โ€” the card uses the lowest-priced variant. Use the “Up to -X%” upgrade if it bothers you.

Badges disappeared after a theme update. Updates replaced the edited files. Re-apply your saved snippets โ€” and this is your reminder to keep them saved.

The badge overlaps the product image awkwardly. Adjust position via Theme settings โ†’ Badges (corner options), or fine-tune with CSS on .badge--bottom-left (margins, font-size).


FAQ

1. How do I show a discount percentage badge on Shopify? Set compare-at prices on your products, then edit two theme files โ€” card-product.liquid and price.liquid โ€” replacing the default “Sale” text with a Liquid calculation of the discount percentage. Full copy-paste code is in this guide.

2. Can I do this without an app? Yes โ€” that’s the whole point. The percentage is a one-line Liquid calculation; paying a monthly app fee for it makes no sense.

3. Why does my Shopify badge only say “Sale”? That’s the theme default. Shopify themes detect a compare-at price and show a translated “Sale” string โ€” displaying the actual percentage requires the small code edit covered here.

4. Does the badge update automatically when I change prices? Yes. The percentage is calculated live from your price and compare-at price every time the page renders โ€” change either price and the badge recalculates instantly.

5. Can the badge show the dollar amount saved instead? Yes โ€” use the “Save $X” variation with the money filter. As a rule of thumb, percentages persuade better under ~$100 and amounts better above it.

6. What happens with variants that have different discounts? The collection card shows the lowest-priced variant’s discount by default; the product page badge updates per selected variant automatically. For grids, the “Up to -X%” code in this guide shows the deepest discount honestly.

7. Will this work on Dawn? Other themes? Written for Dawn, works on Sense, Craft, Refresh, Studio, and other Dawn-based themes with near-identical code. Other OS 2.0 themes use the same technique โ€” find their product card and price snippets and swap the badge text there.

8. Can I only show badges for discounts above a certain size? Yes โ€” wrap the badge in {% if discount_pct >= 10 %} (or any threshold). Hiding tiny discounts is usually smart; a “-3%” badge undermines more than it sells.

9. How do I change the badge color? Dawn has a built-in setting under Theme settings โ†’ Badges (color scheme + position). For full control, the CSS in this guide styles it as a red pill โ€” adjust to your brand.

10. Is it legal to round the percentage up? Risky. Displaying “25%” for a 23% discount can breach advertising standards in many regions. Round down (floor) or display exact rounded values to stay safe.

11. Do discount badges actually increase sales? Specific-number badges consistently outperform generic “Sale” labels because they let shoppers evaluate the deal from the grid, before clicking. They amplify genuine discounts โ€” they can’t rescue weak ones.

12. Will this slow down my store? No. It’s a server-side Liquid calculation โ€” zero JavaScript, zero external requests, zero measurable impact.

13. Will my changes survive theme updates? No โ€” updates replace both edited files. Save your snippets (they’re short) and re-apply after updating; it takes about five minutes.

14. The badge shows “-0%” on some products โ€” why? The discount is under 0.5% and rounds to zero, or the integer-math bug is present (times: 100 without the .0). Fix the decimal, and consider the minimum-threshold upgrade so trivial discounts show no badge at all.


Conclusion

The difference between “Sale” and “-40%” is one Liquid calculation โ€” and a measurable difference in clicks from your collection pages. Shopify already has every number it needs; your theme just never does the math. Now yours does.

To recap: set honest compare-at prices, replace the badge text in card-product.liquid for your grids and price.liquid for the product page with the percentage calculation (remember the 100.0), style the badge to your brand, and pick your upgrades โ€” nearest-5 rounding, a minimum threshold, “Up to -X%” for mixed-discount variants, or the “Save $X” amount for high-ticket items.

Twenty minutes, two files, no app, no subscription โ€” and every discount in your store now advertises itself with a number instead of a whisper.

If you want to keep adding conversion-focused customizations like this, the resources below are your next step.


Keep Learning and Get Help

Prefer to follow along on video? Watch the full step-by-step walkthrough of this exact customization here: How To Show Discount/Sale Badge On Shopify (Percentage) โ€” and subscribe for more free-code Shopify tutorials: youtube.com/@foysalshopifyexpert

Want it installed for you? If you’d rather have an expert set up your discount badges โ€” or handle any Shopify customization, theme work, or SEO project โ€” hire me directly on Upwork: My Upwork Profile

Ready-made Shopify sections and blocks. I sell polished, plug-and-play Shopify code products โ€” sale and urgency blocks, popups, sliders, mega menus, and more โ€” with one-time payment and no subscriptions: ecommercethesis.gumroad.com

Learn Shopify professionally. Turn customizations like this into freelancing income with my structured course: Freelancing with Shopify โ€” or browse all courses.

Stuck on a step? Reach me directly on WhatsApp for personal help: wa.me/8801991505652

Happy selling โ€” and may your badges always show big numbers.