Add a Free Shipping Progress Bar to Your Shopify Store
Boost Sales by Showing Customers How Close They Are to Free Shipping
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.
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:
- In the Sections folder, create a new file called "free-shipping-bar.liquid"
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 %}
- 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"
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:
- 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.
- 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.
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.