< Back to all blog posts

Add a Free Shipping Progress Bar to Your Shopify Store

Boost Sales by Showing Customers How Close They Are to Free Shipping

Add a free shipping progress bar to your Shopify store
Peter is the author and developer of the Better Shipping app for ShopifyBy Peter
01/13/2025

Learn how to add a progress bar that shows customers how much more they need to spend to get free shipping.

One thing I always recommend to merchants using Better Shipping's free shipping threshold is to make sure customers know exactly how close they are to free shipping. The best way to do this? Add a progress bar to your cart page that shows "Add $X more for free shipping!"

I've seen conversion rates jump by 15-20% just by adding this simple feature. Today I'll show you how to add it to your Shopify theme.

Better Shipping for Shopify add free shipping bar screenshot

First, you'll need to create a new section that can be added anywhere in your theme. In your Shopify admin, go to Online Store > Themes > ... (three dots) > Edit code. We'll create two new files:

Better Shipping for Shopify online store edit code screenshot
  1. In the Sections folder, create a new file called "free-shipping-bar.liquid"
Better Shipping for Shopify add free shipping bar code snippet screenshot

And replace everything in the file with this code (it is a little long, but it is designed to work in all situations):

{%- style -%}
  .shipping-bar-container {
    display: block !important;
    width: 100%;
    position: sticky;
    top: calc(var(--header-bottom-position-desktop, 0px) + 0px);
    z-index: 0;
    margin-bottom: -1px;
    transition: opacity 0.3s ease;
    /* Ensure it's visible without JS */
    opacity: 1;
  }

  @media screen and (max-width: 990px) {
    .shipping-bar-container {
      top: calc(var(--header-bottom-position-mobile, 0px) + 0px);
    }
  }

  /* Only hide with JS enabled */
  .js .shipping-bar-container.shipping-bar--hidden {
    opacity: 0;
    pointer-events: none;
  }

  .shipping-bar {
    padding: 1rem;
    margin: 0;
    text-align: center;
    background: rgba(var(--color-foreground), 0.03);
    display: block !important;
    box-shadow: 0 1px 3px rgba(var(--color-foreground), 0.1);
  }

  .shipping-bar--success {
    background: var(--gradient-success);
    color: rgb(var(--color-foreground));
  }

  .shipping-bar--progress {
    background: rgba(var(--color-foreground), 0.04);
  }

  .progress {
    max-width: 300px;
    margin: 0.5rem auto 0;
    background: rgba(var(--color-foreground), 0.08);
    border-radius: var(--buttons-radius-outset);
    height: 8px;
    overflow: hidden;
    display: block !important;
  }

  .progress-bar {
    background: rgba(var(--color-button), 0.9);
    height: 100%;
    transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
    display: block !important;
    min-width: 0%;
  }

  @media screen and (max-width: 749px) {
    .shipping-bar {
      font-size: 0.9rem;
      padding: 0.75rem;
    }
  }
{%- endstyle -%}

<script>document.documentElement.classList.add('js');</script>

{%- liquid
  assign free_shipping_threshold = section.settings.threshold | times: 100
  assign current_cart_total = cart.total_price
-%}

<div 
  class="shipping-bar-container{% if cart.item_count == 0 %} shipping-bar--hidden{% endif %}"
  data-threshold="{{ free_shipping_threshold }}"
>
  {% if cart.item_count > 0 %}
    {% if current_cart_total >= free_shipping_threshold %}
      <div class="shipping-bar shipping-bar--success">
        <span class="h5">🎉 Your order qualifies for free shipping!</span>
      </div>
    {% else %}
      {% assign remaining_amount = free_shipping_threshold | minus: current_cart_total %}
      {% assign progress_percentage = current_cart_total | times: 100 | divided_by: free_shipping_threshold %}
      <div class="shipping-bar shipping-bar--progress">
        <span class="h5" data-free-shipping-message>Add {{ remaining_amount | money }} more for free shipping</span>
        <div class="progress">
          <div class="progress-bar" data-progress-bar style="width: {{ progress_percentage }}%"></div>
        </div>
      </div>
    {% endif %}
  {% endif %}
</div>

<script>
    class ShippingBar {
    constructor() {
        this.container = document.querySelector('.shipping-bar-container');
        if (!this.container) return;

        // Get threshold from data attribute
        this.threshold = parseInt(this.container.dataset.threshold) || 10000;

        this.messageElement = this.container.querySelector('[data-free-shipping-message]');
        this.progressBar = this.container.querySelector('[data-progress-bar]');
        
        // Listen for Shopify's cart changes
        document.addEventListener('cart:refresh', this.handleCartUpdate.bind(this));
        document.addEventListener('cart:change', this.handleCartUpdate.bind(this));
        document.addEventListener('product:added', this.handleCartUpdate.bind(this));
        document.addEventListener('cart:updated', this.handleCartUpdate.bind(this));
        
        // Initial cart fetch
        this.handleCartUpdate();
        
        // Setup fetch listener
        this.setupFetchListener();
    }

    handleCartUpdate() {
        fetch('/cart.js')
        .then(response => response.json())
        .then(cart => {
            // Only show and update if there are items in the cart
            if (cart.item_count > 0) {
            this.updateProgress(cart.total_price);
            } else {
            this.container.classList.add('shipping-bar--hidden');
            }
        })
        .catch(error => console.error('Error fetching cart:', error));
    }

    setupFetchListener() {
        const originalFetch = window.fetch;
        window.fetch = async (...args) => {
        const response = await originalFetch(...args);
        
        const url = args[0].url || args[0];
        
        if (typeof url === 'string' && 
            (url.includes('/cart/add') || 
            url.includes('/cart/change') || 
            url.includes('/cart/update') ||
            url.includes('/cart/clear'))) {
            setTimeout(() => this.handleCartUpdate(), 100);
        }
        
        return response;
        };
    }

    updateProgress(cartTotal) {
        if (!this.container) return;

        // Hide the bar if cart is empty
        if (cartTotal === 0) {
        this.container.classList.add('shipping-bar--hidden');
        return;
        } else {
        this.container.classList.remove('shipping-bar--hidden');
        }

        if (cartTotal >= this.threshold) {
        this.container.innerHTML = `
            <div class="shipping-bar shipping-bar--success">
            <span class="h5">🎉 Your order qualifies for free shipping!</span>
            </div>
        `;
        } else {
        const remaining = this.threshold - cartTotal;
        const percentage = (cartTotal * 100) / this.threshold;
        let message = `Add ${this.formatMoney(remaining)} more for free shipping`;

        // Create the new content but keep the progress bar separate
        const progressBar = this.container.querySelector('.progress-bar');
        
        if (progressBar) {
            // If progress bar exists, update its width with animation
            progressBar.style.width = `${percentage}%`;
            
            // Update the message separately to avoid disrupting the animation
            const messageEl = this.container.querySelector('.h5');
            if (messageEl) {
            messageEl.textContent = message;
            }
        } else {
            // If no progress bar exists, create the entire content
            this.container.innerHTML = `
            <div class="shipping-bar shipping-bar--progress">
                <span class="h5">${message}</span>
                <div class="progress">
                <div class="progress-bar" style="width: ${percentage}%"></div>
                </div>
            </div>
            `;
        }
        }
    }

    formatMoney(cents) {
        return (cents / 100).toLocaleString('en-US', {
        style: 'currency',
        currency: 'AUD'
        });
    }
    }

    // Initialize when DOM is loaded
    document.addEventListener('DOMContentLoaded', () => {
    new ShippingBar();
    });
</script>

{% schema %}
{
  "name": "Free Shipping Bar",
  "settings": [
    {
      "type": "number",
      "id": "threshold",
      "label": "Free Shipping Threshold",
      "default": 100,
      "info": "Enter the amount in dollars (not cents)"
    }
  ],
  "presets": [
    {
      "name": "Free Shipping Bar"
    }
  ]
}
{% endschema %}
  1. Now, to add it to your theme, go to Online Store > Themes > Customize. In the theme editor:
    • Click "Add section"
    • Look for "Free Shipping Bar"
    • Add it where you want it to appear (usually at the top of the page)
    • Click "Save"
Better Shipping for Shopify add free shipping bar in customize theme screenshot

The progress bar will now show up on every page when there are items in the cart, and it will update automatically as customers add or remove items.

Want to make it even better? Here are some cool tweaks you can add:

  1. Show different messages based on how close they are. Replace this section of code:
let message = `Add ${this.formatMoney(remaining)} more for free shipping`;

with this:

let message = `Add ${this.formatMoney(remaining)} more for free shipping`;

if (remaining < 1000) {
   message = `Almost there! Just ${this.formatMoney(remaining)} away from free shipping! 🎉`;
} else if (remaining < 3000) {
   message = `You're getting close! Add ${this.formatMoney(remaining)} more for free shipping`;
} else if (remaining < 5000) {
   message = `Add ${this.formatMoney(remaining)} more to get free shipping on your order`;
} else {
   message = `Free shipping when you add ${this.formatMoney(remaining)} more to your cart`;
}

This will show different encouraging messages based on how close they are to free shipping:

  • Less than $10 away: Shows an excited "Almost there!" message
  • Less than $30 away: Shows they're getting close
  • Less than $50 away: Standard message
  • More than $50 away: Focuses on the free shipping offer

Remember that the amounts are in cents (1000 = $10), so adjust these thresholds to match your free shipping threshold.

  1. Add some animation when they qualify:
.shipping-bar--success {
  background: var(--gradient-success);
  color: rgb(var(--color-foreground));
  animation: pulse 2s infinite;
}

@keyframes pulse {
  0% { transform: scale(1); }
  50% { transform: scale(1.02); }
  100% { transform: scale(1); }
}

Remember to test this on mobile too - that's where most of your customers will see it. And make sure the threshold matches what you've set up in Better Shipping! Not sure what threshold to use? Check out our guide on how to calculate your free shipping threshold.

Once you've got this set up, watch your average order value for a few weeks. You might be surprised how many customers will add just one more item to hit that free shipping threshold! Want to learn more ways to use shipping to increase sales? Read our guide on using shipping as a marketing tool.

Better Shipping for Shopify your order qualifies for free shipping screenshot

Ready to try these strategies in your store? You can test them out with Better Shipping's 14-day free trial on the Shopify App Store.


Peter is the author and developer of the Better Shipping app for ShopifyBy Peter
01/13/2025