Shopify Development⏱ 4 min read

How to Add a Free Shipping Progress Bar to Shopify Dawn (Liquid Snippet)

Learn how to build a dynamic, real-time free shipping threshold progress bar in Shopify Dawn using native Liquid and Ajax cart endpoints with zero app fees.

Muhammad Bilal ShakeelWritten by Muhammad Bilal Shakeel
β€’Published: 2026-08-15β€’Updated: 2026-09-18
Advertisement

Offering a dynamic Free Shipping Progress Bar is one of the most effective methods to increase Average Order Value (AOV) on Shopify. By visually illustrating how close a shopper is to unlocking complimentary delivery (e.g. "Add $14.00 more to unlock Free Shipping!"), merchants incentivize adding complementary items before checkout. Below is a complete Liquid and JavaScript implementation guide for Shopify Dawn and Online Store 2.0 themes.

⚑ Engineering Requirements: Server Liquid + Client Ajax Synchronization

A resilient progress bar requires two distinct synchronization layers: (1) Liquid arithmetic that computes the remaining deficit and percentage progress on initial page load, and (2) JavaScript event hooks that dynamically update the progress bar width and message when shoppers modify cart quantities in Ajax drawers without triggering full page reloads.

1. Production Liquid Snippet: snippets/free-shipping-bar.liquid

Create a new snippet under Snippets β†’ Add a new snippet named free-shipping-bar.liquid and insert the following code:

{% comment %}
  snippets/free-shipping-bar.liquid
  Configurable Free Shipping Threshold in Cents (e.g., $75.00 = 7500 cents)
{% endcomment %}
{% assign threshold = 7500 %}
{% assign current_total = cart.total_price %}
{% assign remaining = threshold | minus: current_total %}

{% if current_total >= threshold %}
  {% assign progress_percent = 100 %}
{% else %}
  {% assign progress_percent = current_total | times: 100.0 | divided_by: threshold | round %}
{% endif %}

<div id="free-shipping-bar" class="shipping-bar-container" data-threshold="{{ threshold }}">
  <p class="shipping-bar-message">
    {% if current_total >= threshold %}
      <span class="unlocked">πŸŽ‰ Congratulations! You have unlocked <strong>FREE Shipping</strong>!</span>
    {% else %}
      Add <strong class="remaining-amount">{{ remaining | money }}</strong> more to unlock <strong>FREE Shipping</strong>!
    {% endif %}
  </p>
  <div class="shipping-bar-track">
    <div class="shipping-bar-progress" style="width: {{ progress_percent }}%;"></div>
  </div>
</div>

2. Styling the Bar: assets/base.css

Add this performant, hardware-accelerated CSS to the bottom of assets/base.css:

.shipping-bar-container {
  padding: 12px 16px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 12px;
  margin: 12px 0;
  text-align: center;
}
.shipping-bar-message {
  font-size: 13px;
  color: #e5e7eb;
  margin-bottom: 8px;
}
.shipping-bar-track {
  width: 100%;
  height: 8px;
  background: rgba(255, 255, 255, 0.15);
  border-radius: 999px;
  overflow: hidden;
}
.shipping-bar-progress {
  height: 100%;
  background: linear-gradient(90deg, #ff6b35, #10b981);
  border-radius: 999px;
  transition: width 0.35s ease-in-out;
}

3. Ajax Cart Drawer Hook: assets/cart-drawer.js

To update the progress bar without reloading the page, attach a JavaScript listener to cart mutation events. In Dawn's cart-drawer.js, update the render cycle:

// Update shipping progress bar when cart state changes
function updateShippingBar(cart) {
  const bar = document.getElementById('free-shipping-bar');
  if (!bar) return;

  const threshold = parseInt(bar.dataset.threshold, 10);
  const currentTotal = cart.total_price;
  const remaining = threshold - currentTotal;
  const progressEl = bar.querySelector('.shipping-bar-progress');
  const messageEl = bar.querySelector('.shipping-bar-message');

  if (currentTotal >= threshold) {
    progressEl.style.width = '100%';
    messageEl.innerHTML = '<span class="unlocked">πŸŽ‰ Congratulations! You have unlocked <strong>FREE Shipping</strong>!</span>';
  } else {
    const percent = Math.min(100, Math.round((currentTotal / threshold) * 100));
    progressEl.style.width = percent + '%';
    const formattedRemaining = '$' + (remaining / 100).toFixed(2);
    messageEl.innerHTML = 'Add <strong class="remaining-amount">' + formattedRemaining + '</strong> more to unlock <strong>FREE Shipping</strong>!';
  }
}

4. Shopify Markets & Multi-Currency Considerations

If your store operates in multiple countries via Shopify Markets, a hardcoded $75.00 threshold will produce inaccurate targets in GBP, EUR, or JPY. To support currency localization:

  • Store market thresholds in Shopify Metafields or theme settings keyed to localization.country.currency.iso_code.
  • Use Shopify's native | money filter so currency symbols and regional decimal conventions match the shopper's active locale.

5. Multi-Tier Progression: Free Shipping to Free Gift Stacking

High-growth brands often stack rewards to push Average Order Value even higher. For example: Tier 1 ($50) unlocks Free Standard Shipping, while Tier 2 ($100) unlocks a Free Full-Size Mystery Gift. In Liquid, you can chain these milestones using conditional branching:

{% assign tier_1 = 5000 %}{% comment %} $50 Free Shipping {% endcomment %}
{% assign tier_2 = 10000 %}{% comment %} $100 Free Gift {% endcomment %}

{% if cart.total_price < tier_1 %}
  {% assign diff = tier_1 | minus: cart.total_price %}
  Add {{ diff | money }} for <strong>Free Shipping</strong>!
{% elsif cart.total_price >= tier_1 and cart.total_price < tier_2 %}
  {% assign diff = tier_2 | minus: cart.total_price %}
  You unlocked Free Shipping! Add {{ diff | money }} to unlock a <strong>Free Gift</strong>! 🎁
{% else %}
  πŸŽ‰ All Rewards Unlocked! Free Shipping + Free Gift included!
{% endif %}

6. Frequently Asked Questions

Why doesn't the progress bar update when shoppers use collection quick-add buttons?

Collection quick-add forms often use separate Ajax fetch requests that bypass the cart drawer's listener. To fix this, ensure your theme emits a custom JavaScript event (such as document.dispatchEvent(new CustomEvent('cart:updated', { detail: { cart } }))) inside your global Ajax add-to-cart handler, and listen to that event inside your shipping bar script.

Does adding this custom snippet impact store speed or INP?

No. Unlike third-party apps that load external React runtimes and analytics trackers over network connections, this pure Liquid and vanilla JavaScript implementation weighs under 2KB, executes in under 1 millisecond, and creates zero layout shift (CLS) because its dimensions are reserved in CSS.

Optimize Your Cart Drawer Architecture

Want to implement native tier incentives, slide-out upsells, and free gift thresholds without monthly app fees? Review our Shopify Development services or explore our App-Free Cart Drawer Engineering Guide.

Advertisement
❓

Frequently Asked Questions

Why build a native Free Shipping Bar instead of using an app?

Native Liquid/JS shipping bars load with 0ms delay, avoid monthly app fees ($10-$30/mo), and update dynamically with the cart drawer via Ajax Cart API.

How do you calculate the remaining amount for free shipping in Liquid?

Subtract `cart.total_price` from your target shipping threshold (e.g. `10000` for $100.00) and format the difference using the `| money` filter.

Does the shipping bar update when customers change quantities in the cart?

Yes, by listening to cart change events in JavaScript and recalculating the percentage width of the CSS progress bar.

Muhammad Bilal Shakeel (Goofy Developer)

Founder of Build With Goofy. Specializing in bespoke Shopify Liquid development, Core Web Vitals & mobile performance engineering, and automated GoHighLevel CRM workflows for clients across the USA, Canada, UK, and worldwide.

⚑ Shopify Store Optimization & Development

Need Help Fixing This on Your Store?

Send us your Shopify store URL for a comprehensive speed, Liquid code, and conversion architecture review.

πŸ’¬ WhatsApp Bilal Directly

Related Developer Guides & Systems

View all guides β†’