JAVASCRIPTshopifybeginner

Shopify Free Shipping Progress Bar

Show customers how much more they need to spend for free shipping

#shopify#shipping#cart#upsell#conversion
Share this snippet:

Code

javascript
1// Free Shipping Bar Class
2class 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
101document.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