JAVASCRIPTshopifybeginner
Shopify Free Shipping Progress Bar
Show customers how much more they need to spend for free shipping
Faisal Yaqoob
November 25, 2025
#shopify#shipping#cart#upsell#conversion
Code
javascript
1 // Free Shipping Bar Class 2 class FreeShippingBar { 3 constructor(options = {}) { 4 this.threshold = options.threshold || 5000; // Amount in cents 5 this.container = document.querySelector(options.container || '[data-shipping-bar]'); 6
7 if (!this.container) { 8 console.error('Shipping bar container not found'); 9 return; 10 } 11
12 this.init(); 13 } 14
15 async init() { 16 // Get current cart 17 await this.updateCart(); 18
19 // Listen for cart updates 20 document.addEventListener('cart:updated', () => this.updateCart()); 21 } 22
23 async updateCart() { 24 try { 25 const response = await fetch('/cart.js'); 26 const cart = await response.json(); 27
28 this.render(cart.total_price); 29 } catch (error) { 30 console.error('Failed to fetch cart:', error); 31 } 32 } 33
34 render(cartTotal) { 35 const remaining = this.threshold - cartTotal; 36 const percentage = Math.min((cartTotal / this.threshold) * 100, 100); 37
38 if (cartTotal >= this.threshold) { 39 this.container.innerHTML = this.getQualifiedTemplate(); 40 } else { 41 this.container.innerHTML = this.getProgressTemplate(remaining, percentage); 42 } 43
44 this.container.classList.add('is-visible'); 45 } 46
47 getProgressTemplate(remaining, percentage) { 48 const formattedAmount = this.formatMoney(remaining); 49
50 return ` 51 <div class="shipping-bar"> 52 <div class="shipping-bar-message"> 53 ${this.getMessageIcon()} 54 You're <strong>${formattedAmount}</strong> away from <strong>FREE SHIPPING</strong>! 55 </div> 56
57 <div class="shipping-bar-progress"> 58 <div class="shipping-bar-fill" style="width: ${percentage}%"> 59 <span class="shipping-bar-percentage">${Math.round(percentage)}%</span> 60 </div> 61 </div> 62 </div> 63 `; 64 } 65
66 getQualifiedTemplate() { 67 return ` 68 <div class="shipping-bar shipping-bar--qualified"> 69 <div class="shipping-bar-message"> 70 ${this.getSuccessIcon()} 71 <strong>Congratulations!</strong> You've qualified for FREE SHIPPING! 72 </div> 73 </div> 74 `; 75 } 76
77 getMessageIcon() { 78 return ` 79 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 80 <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/> 81 <circle cx="9" cy="21" r="1"/> 82 <circle cx="20" cy="21" r="1"/> 83 </svg> 84 `; 85 } 86
87 getSuccessIcon() { 88 return ` 89 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 90 <polyline points="20 6 9 17 4 12"/> 91 </svg> 92 `; 93 } 94
95 formatMoney(cents) { 96 return '$' + (cents / 100).toFixed(2); 97 } 98 } 99
100 // Initialize 101 document.addEventListener('DOMContentLoaded', () => { 102 window.freeShippingBar = new FreeShippingBar({ 103 threshold: 5000, // $50.00 in cents 104 container: '[data-shipping-bar]' 105 }); 106 });
Shopify Free Shipping Progress Bar
Display a dynamic progress bar showing customers how close they are to qualifying for free shipping, encouraging them to add more items to their cart.
// Free Shipping Bar Class
class FreeShippingBar {
constructor(options = {}) {
this.threshold = options.threshold || 5000; // Amount in cents
this.container = document.querySelector(options.container || '[data-shipping-bar]');
if (!this.container) {
console.error('Shipping bar container not found');
return;
}
this.init();
}
async init() {
// Get current cart
await this.updateCart();
// Listen for cart updates
document.addEventListener('cart:updated', () => this.updateCart());
}
async updateCart() {
try {
const response = await fetch('/cart.js');
const cart = await response.json();
this.render(cart.total_price);
} catch (error) {
console.error('Failed to fetch cart:', error);
}
}
render(cartTotal) {
const remaining = this.threshold - cartTotal;
const percentage = Math.min((cartTotal / this.threshold) * 100, 100);
if (cartTotal >= this.threshold) {
this.container.innerHTML = this.getQualifiedTemplate();
} else {
this.container.innerHTML = this.getProgressTemplate(remaining, percentage);
}
this.container.classList.add('is-visible');
}
getProgressTemplate(remaining, percentage) {
const formattedAmount = this.formatMoney(remaining);
return `
<div class="shipping-bar">
<div class="shipping-bar-message">
${this.getMessageIcon()}
You're <strong>${formattedAmount}</strong> away from <strong>FREE SHIPPING</strong>!
</div>
<div class="shipping-bar-progress">
<div class="shipping-bar-fill" style="width: ${percentage}%">
<span class="shipping-bar-percentage">${Math.round(percentage)}%</span>
</div>
</div>
</div>
`;
}
getQualifiedTemplate() {
return `
<div class="shipping-bar shipping-bar--qualified">
<div class="shipping-bar-message">
${this.getSuccessIcon()}
<strong>Congratulations!</strong> You've qualified for FREE SHIPPING!
</div>
</div>
`;
}
getMessageIcon() {
return `
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/>
<circle cx="9" cy="21" r="1"/>
<circle cx="20" cy="21" r="1"/>
</svg>
`;
}
getSuccessIcon() {
return `
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="20 6 9 17 4 12"/>
</svg>
`;
}
formatMoney(cents) {
return '$' + (cents / 100).toFixed(2);
}
}
// Initialize
document.addEventListener('DOMContentLoaded', () => {
window.freeShippingBar = new FreeShippingBar({
threshold: 5000, // $50.00 in cents
container: '[data-shipping-bar]'
});
});
Liquid Template
<!-- Add at top of cart or cart drawer -->
<div class="shipping-bar-container" data-shipping-bar>
<!-- Bar will be rendered here by JavaScript -->
</div>
<!-- Alternative: Server-side rendering for initial load -->
{% assign threshold = 5000 %}
{% assign remaining = threshold | minus: cart.total_price %}
<div class="shipping-bar-container" data-shipping-bar>
{% if cart.total_price >= threshold %}
<div class="shipping-bar shipping-bar--qualified">
<div class="shipping-bar-message">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="20 6 9 17 4 12"/>
</svg>
<strong>Congratulations!</strong> You've qualified for FREE SHIPPING!
</div>
</div>
{% else %}
<div class="shipping-bar">
<div class="shipping-bar-message">
You're <strong>{{ remaining | money }}</strong> away from <strong>FREE SHIPPING</strong>!
</div>
<div class="shipping-bar-progress">
{% assign percentage = cart.total_price | times: 100 | divided_by: threshold %}
<div class="shipping-bar-fill" style="width: {{ percentage }}%">
<span class="shipping-bar-percentage">{{ percentage }}%</span>
</div>
</div>
</div>
{% endif %}
</div>
CSS Styling
.shipping-bar-container {
position: sticky;
top: 0;
z-index: 100;
opacity: 0;
transform: translateY(-100%);
transition: all 0.3s ease;
}
.shipping-bar-container.is-visible {
opacity: 1;
transform: translateY(0);
}
.shipping-bar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
padding: 15px 20px;
text-align: center;
}
.shipping-bar--qualified {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
animation: celebrate 0.5s ease;
}
@keyframes celebrate {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.02); }
}
.shipping-bar-message {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-size: 14px;
margin-bottom: 10px;
}
.shipping-bar-message svg {
flex-shrink: 0;
}
.shipping-bar--qualified .shipping-bar-message {
margin-bottom: 0;
font-size: 15px;
}
.shipping-bar-progress {
background: rgba(255,255,255,0.3);
border-radius: 10px;
height: 20px;
overflow: hidden;
position: relative;
}
.shipping-bar-fill {
background: #fff;
height: 100%;
border-radius: 10px;
transition: width 0.5s ease;
display: flex;
align-items: center;
justify-content: flex-end;
padding: 0 10px;
}
.shipping-bar-percentage {
color: #667eea;
font-size: 12px;
font-weight: bold;
}
/* Mobile Responsive */
@media (max-width: 576px) {
.shipping-bar {
padding: 12px 15px;
}
.shipping-bar-message {
font-size: 13px;
margin-bottom: 8px;
}
.shipping-bar-progress {
height: 16px;
}
.shipping-bar-percentage {
font-size: 11px;
}
}
Advanced: Multi-Tier Shipping
// Support multiple shipping tiers
class MultiTierShippingBar extends FreeShippingBar {
constructor(options = {}) {
super(options);
this.tiers = options.tiers || [
{ threshold: 3000, rate: 500, label: 'Standard Shipping' },
{ threshold: 5000, rate: 0, label: 'FREE SHIPPING' },
{ threshold: 10000, rate: 0, label: 'FREE Express Shipping' },
];
}
render(cartTotal) {
const currentTier = this.getCurrentTier(cartTotal);
const nextTier = this.getNextTier(cartTotal);
if (!nextTier) {
// Reached highest tier
this.container.innerHTML = this.getMaxTierTemplate(currentTier);
} else {
const remaining = nextTier.threshold - cartTotal;
const percentage = ((cartTotal - (currentTier?.threshold || 0)) / (nextTier.threshold - (currentTier?.threshold || 0))) * 100;
this.container.innerHTML = this.getProgressTemplate(remaining, percentage, nextTier.label);
}
this.container.classList.add('is-visible');
}
getCurrentTier(cartTotal) {
return this.tiers.filter(tier => cartTotal >= tier.threshold).pop();
}
getNextTier(cartTotal) {
return this.tiers.find(tier => cartTotal < tier.threshold);
}
getProgressTemplate(remaining, percentage, nextLabel) {
const formattedAmount = this.formatMoney(remaining);
return `
<div class="shipping-bar">
<div class="shipping-bar-message">
Add <strong>${formattedAmount}</strong> more to unlock <strong>${nextLabel}</strong>!
</div>
<div class="shipping-bar-progress">
<div class="shipping-bar-fill" style="width: ${percentage}%">
<span class="shipping-bar-percentage">${Math.round(percentage)}%</span>
</div>
</div>
<div class="shipping-tiers">
${this.renderTiers(cartTotal)}
</div>
</div>
`;
}
renderTiers(cartTotal) {
return this.tiers.map(tier => {
const isAchieved = cartTotal >= tier.threshold;
const isCurrent = !isAchieved && cartTotal >= (this.getCurrentTier(cartTotal)?.threshold || 0);
return `
<div class="tier ${isAchieved ? 'achieved' : ''} ${isCurrent ? 'current' : ''}">
${this.formatMoney(tier.threshold)} - ${tier.label}
</div>
`;
}).join('');
}
getMaxTierTemplate(tier) {
return `
<div class="shipping-bar shipping-bar--qualified">
<div class="shipping-bar-message">
🎉 You've unlocked <strong>${tier.label}</strong>!
</div>
</div>
`;
}
}
Cart Page Placement
<!-- In cart.liquid or cart drawer -->
<div class="cart-header">
{% render 'free-shipping-bar' %}
</div>
<div class="cart-items">
<!-- Cart items -->
</div>
Features
- Visual Progress: Clear visual indicator of progress
- Dynamic Updates: Updates in real-time without refresh
- Celebration: Special message when threshold reached
- Customizable: Easy to change threshold amount
- Multi-Tier Support: Advanced version with multiple levels
- Responsive: Mobile-friendly design
- Sticky Position: Stays visible while scrolling
- Conversion Focused: Encourages higher cart values
Related Snippets
Shopify Sticky Add to Cart Bar
Keep the add to cart button visible as users scroll down product pages
JAVASCRIPTshopifybeginner
javascriptPreview
// Sticky Add to Cart Class
class StickyAddToCart {
constructor(options = {}) {
this.mainButton = document.querySelector(options.mainButton || '[data-main-add-to-cart]');
...#shopify#sticky#add-to-cart+2
11/22/2025
View
Shopify Multi-Currency Converter
Add multi-currency support with automatic conversion and detection
JAVASCRIPTshopifyadvanced
javascriptPreview
// Currency Converter Class
class CurrencyConverter {
constructor(options = {}) {
this.shopCurrency = options.shopCurrency || 'USD';
...#shopify#currency#multi-currency+2
11/19/2025
View
Shopify Slide-Out Cart Drawer
Create a modern slide-out cart drawer with AJAX functionality
JAVASCRIPTshopifyintermediate
javascriptPreview
// Cart Drawer Functionality
class CartDrawer {
constructor() {
this.drawer = document.querySelector('[data-cart-drawer]');
...#shopify#cart#ajax+2
11/16/2025
View