JAVASCRIPTshopifybeginner
Shopify Sticky Add to Cart Bar
Keep the add to cart button visible as users scroll down product pages
Faisal Yaqoob
November 22, 2025
#shopify#sticky#add-to-cart#scroll#conversion
Code
javascript
1 // Sticky Add to Cart Class 2 class StickyAddToCart { 3 constructor(options = {}) { 4 this.mainButton = document.querySelector(options.mainButton || '[data-main-add-to-cart]'); 5 this.stickyBar = document.querySelector(options.stickyBar || '[data-sticky-cart]'); 6 this.threshold = options.threshold || 100; 7
8 if (!this.mainButton || !this.stickyBar) { 9 console.error('Required elements not found'); 10 return; 11 } 12
13 this.isVisible = false; 14 this.init(); 15 } 16
17 init() { 18 // Watch scroll 19 window.addEventListener('scroll', () => this.handleScroll(), { passive: true }); 20
21 // Sync quantity and variant selection 22 this.syncFormData(); 23
24 // Handle sticky bar add to cart 25 this.handleStickySubmit(); 26
27 // Initial check 28 this.handleScroll(); 29 } 30
31 handleScroll() { 32 const mainButtonRect = this.mainButton.getBoundingClientRect(); 33 const mainButtonVisible = mainButtonRect.top >= 0 && mainButtonRect.bottom <= window.innerHeight; 34
35 if (!mainButtonVisible && window.scrollY > this.threshold) { 36 this.show(); 37 } else { 38 this.hide(); 39 } 40 } 41
42 show() { 43 if (!this.isVisible) { 44 this.stickyBar.classList.add('is-visible'); 45 this.isVisible = true; 46 } 47 } 48
49 hide() { 50 if (this.isVisible) { 51 this.stickyBar.classList.remove('is-visible'); 52 this.isVisible = false; 53 } 54 } 55
56 syncFormData() { 57 // Sync variant selection 58 const mainVariantSelect = document.querySelector('[name="id"]'); 59 const stickyVariantSelect = this.stickyBar.querySelector('[name="id"]'); 60
61 if (mainVariantSelect && stickyVariantSelect) { 62 mainVariantSelect.addEventListener('change', (e) => { 63 stickyVariantSelect.value = e.target.value; 64 this.updateStickyPrice(); 65 }); 66 } 67
68 // Sync quantity 69 const mainQuantity = document.querySelector('[data-main-quantity]'); 70 const stickyQuantity = this.stickyBar.querySelector('[data-sticky-quantity]'); 71
72 if (mainQuantity && stickyQuantity) { 73 mainQuantity.addEventListener('change', (e) => { 74 stickyQuantity.value = e.target.value; 75 }); 76
77 stickyQuantity.addEventListener('change', (e) => { 78 mainQuantity.value = e.target.value; 79 }); 80 } 81 } 82
83 handleStickySubmit() { 84 const form = this.stickyBar.querySelector('form'); 85
86 if (!form) return; 87
88 form.addEventListener('submit', async (e) => { 89 e.preventDefault(); 90
91 const formData = new FormData(form); 92 const data = { 93 items: [{ 94 id: formData.get('id'), 95 quantity: parseInt(formData.get('quantity') || 1) 96 }] 97 }; 98
99 try { 100 const response = await fetch('/cart/add.js', { 101 method: 'POST', 102 headers: { 103 'Content-Type': 'application/json', 104 }, 105 body: JSON.stringify(data) 106 }); 107
108 if (response.ok) { 109 this.showSuccess(); 110 // Trigger cart update 111 document.dispatchEvent(new CustomEvent('cart:updated')); 112 } else { 113 this.showError(); 114 } 115 } catch (error) { 116 console.error('Add to cart error:', error); 117 this.showError(); 118 } 119 }); 120 } 121
122 updateStickyPrice() { 123 const variantSelect = this.stickyBar.querySelector('[name="id"]'); 124 const priceElement = this.stickyBar.querySelector('[data-sticky-price]'); 125
126 if (!variantSelect || !priceElement) return; 127
128 const selectedOption = variantSelect.options[variantSelect.selectedIndex]; 129 const price = selectedOption.dataset.price; 130
131 if (price) { 132 priceElement.textContent = this.formatMoney(price); 133 } 134 } 135
136 showSuccess() { 137 const button = this.stickyBar.querySelector('button[type="submit"]'); 138 const originalText = button.textContent; 139
140 button.textContent = 'Added! ✓'; 141 button.classList.add('success'); 142
143 setTimeout(() => { 144 button.textContent = originalText; 145 button.classList.remove('success'); 146 }, 2000); 147 } 148
149 showError() { 150 const button = this.stickyBar.querySelector('button[type="submit"]'); 151 const originalText = button.textContent; 152
153 button.textContent = 'Error!'; 154 button.classList.add('error'); 155
156 setTimeout(() => { 157 button.textContent = originalText; 158 button.classList.remove('error'); 159 }, 2000); 160 } 161
162 formatMoney(cents) { 163 return '$' + (parseInt(cents) / 100).toFixed(2); 164 } 165 } 166
167 // Initialize 168 document.addEventListener('DOMContentLoaded', () => { 169 new StickyAddToCart({ 170 mainButton: '[data-main-add-to-cart]', 171 stickyBar: '[data-sticky-cart]', 172 threshold: 150 173 }); 174 });
Shopify Sticky Add to Cart Bar
Create a sticky add to cart bar that appears when the main add to cart button scrolls out of view, improving conversion rates especially on mobile devices.
// Sticky Add to Cart Class
class StickyAddToCart {
constructor(options = {}) {
this.mainButton = document.querySelector(options.mainButton || '[data-main-add-to-cart]');
this.stickyBar = document.querySelector(options.stickyBar || '[data-sticky-cart]');
this.threshold = options.threshold || 100;
if (!this.mainButton || !this.stickyBar) {
console.error('Required elements not found');
return;
}
this.isVisible = false;
this.init();
}
init() {
// Watch scroll
window.addEventListener('scroll', () => this.handleScroll(), { passive: true });
// Sync quantity and variant selection
this.syncFormData();
// Handle sticky bar add to cart
this.handleStickySubmit();
// Initial check
this.handleScroll();
}
handleScroll() {
const mainButtonRect = this.mainButton.getBoundingClientRect();
const mainButtonVisible = mainButtonRect.top >= 0 && mainButtonRect.bottom <= window.innerHeight;
if (!mainButtonVisible && window.scrollY > this.threshold) {
this.show();
} else {
this.hide();
}
}
show() {
if (!this.isVisible) {
this.stickyBar.classList.add('is-visible');
this.isVisible = true;
}
}
hide() {
if (this.isVisible) {
this.stickyBar.classList.remove('is-visible');
this.isVisible = false;
}
}
syncFormData() {
// Sync variant selection
const mainVariantSelect = document.querySelector('[name="id"]');
const stickyVariantSelect = this.stickyBar.querySelector('[name="id"]');
if (mainVariantSelect && stickyVariantSelect) {
mainVariantSelect.addEventListener('change', (e) => {
stickyVariantSelect.value = e.target.value;
this.updateStickyPrice();
});
}
// Sync quantity
const mainQuantity = document.querySelector('[data-main-quantity]');
const stickyQuantity = this.stickyBar.querySelector('[data-sticky-quantity]');
if (mainQuantity && stickyQuantity) {
mainQuantity.addEventListener('change', (e) => {
stickyQuantity.value = e.target.value;
});
stickyQuantity.addEventListener('change', (e) => {
mainQuantity.value = e.target.value;
});
}
}
handleStickySubmit() {
const form = this.stickyBar.querySelector('form');
if (!form) return;
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
const data = {
items: [{
id: formData.get('id'),
quantity: parseInt(formData.get('quantity') || 1)
}]
};
try {
const response = await fetch('/cart/add.js', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
});
if (response.ok) {
this.showSuccess();
// Trigger cart update
document.dispatchEvent(new CustomEvent('cart:updated'));
} else {
this.showError();
}
} catch (error) {
console.error('Add to cart error:', error);
this.showError();
}
});
}
updateStickyPrice() {
const variantSelect = this.stickyBar.querySelector('[name="id"]');
const priceElement = this.stickyBar.querySelector('[data-sticky-price]');
if (!variantSelect || !priceElement) return;
const selectedOption = variantSelect.options[variantSelect.selectedIndex];
const price = selectedOption.dataset.price;
if (price) {
priceElement.textContent = this.formatMoney(price);
}
}
showSuccess() {
const button = this.stickyBar.querySelector('button[type="submit"]');
const originalText = button.textContent;
button.textContent = 'Added! ✓';
button.classList.add('success');
setTimeout(() => {
button.textContent = originalText;
button.classList.remove('success');
}, 2000);
}
showError() {
const button = this.stickyBar.querySelector('button[type="submit"]');
const originalText = button.textContent;
button.textContent = 'Error!';
button.classList.add('error');
setTimeout(() => {
button.textContent = originalText;
button.classList.remove('error');
}, 2000);
}
formatMoney(cents) {
return '$' + (parseInt(cents) / 100).toFixed(2);
}
}
// Initialize
document.addEventListener('DOMContentLoaded', () => {
new StickyAddToCart({
mainButton: '[data-main-add-to-cart]',
stickyBar: '[data-sticky-cart]',
threshold: 150
});
});
Liquid Template
<!-- Main Product Form -->
<form action="/cart/add" method="post" class="product-form">
<select name="id" data-variant-select>
{% for variant in product.variants %}
<option
value="{{ variant.id }}"
data-price="{{ variant.price }}"
{% unless variant.available %}disabled{% endunless %}>
{{ variant.title }} - {{ variant.price | money }}
</option>
{% endfor %}
</select>
<input type="number" name="quantity" value="1" min="1" data-main-quantity>
<button type="submit" data-main-add-to-cart class="btn btn--add-to-cart">
Add to Cart
</button>
</form>
<!-- Sticky Add to Cart Bar -->
<div class="sticky-cart-bar" data-sticky-cart>
<div class="sticky-cart-content">
<div class="sticky-cart-product">
{% if product.featured_image %}
<img src="{{ product.featured_image | img_url: '80x' }}" alt="{{ product.title }}">
{% endif %}
<div class="sticky-cart-info">
<h4 class="sticky-cart-title">{{ product.title }}</h4>
<div class="sticky-cart-price" data-sticky-price>
{{ product.price | money }}
</div>
</div>
</div>
<form action="/cart/add" method="post" class="sticky-cart-form">
<input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}">
<input
type="number"
name="quantity"
value="1"
min="1"
class="sticky-quantity"
data-sticky-quantity>
<button type="submit" class="btn btn--sticky-cart">
Add to Cart
</button>
</form>
</div>
</div>
CSS Styling
/* Sticky Cart Bar */
.sticky-cart-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
border-top: 1px solid #e0e0e0;
box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
padding: 15px;
transform: translateY(100%);
transition: transform 0.3s ease;
z-index: 999;
}
.sticky-cart-bar.is-visible {
transform: translateY(0);
}
.sticky-cart-content {
max-width: 1200px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
}
.sticky-cart-product {
display: flex;
align-items: center;
gap: 15px;
flex: 1;
}
.sticky-cart-product img {
width: 60px;
height: 60px;
object-fit: cover;
border-radius: 4px;
}
.sticky-cart-info {
flex: 1;
}
.sticky-cart-title {
font-size: 14px;
margin: 0 0 5px 0;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.sticky-cart-price {
font-size: 16px;
font-weight: bold;
}
.sticky-cart-form {
display: flex;
align-items: center;
gap: 10px;
}
.sticky-quantity {
width: 60px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
text-align: center;
}
.btn--sticky-cart {
padding: 12px 30px;
background: #000;
color: #fff;
border: none;
border-radius: 4px;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
transition: background 0.3s;
}
.btn--sticky-cart:hover {
background: #333;
}
.btn--sticky-cart.success {
background: #4caf50;
}
.btn--sticky-cart.error {
background: #f44336;
}
/* Mobile Responsive */
@media (max-width: 768px) {
.sticky-cart-content {
flex-wrap: wrap;
}
.sticky-cart-title {
font-size: 13px;
}
.sticky-cart-price {
font-size: 14px;
}
.btn--sticky-cart {
padding: 10px 20px;
font-size: 14px;
}
}
@media (max-width: 576px) {
.sticky-cart-bar {
padding: 10px;
}
.sticky-cart-product img {
width: 50px;
height: 50px;
}
.sticky-quantity {
width: 50px;
padding: 8px;
}
.btn--sticky-cart {
flex: 1;
}
}
With Variant Selector
// Extended version with variant options in sticky bar
class StickyAddToCartAdvanced extends StickyAddToCart {
init() {
super.init();
this.createVariantSelector();
}
createVariantSelector() {
const mainSelect = document.querySelector('[data-variant-select]');
const stickyForm = this.stickyBar.querySelector('.sticky-cart-form');
if (!mainSelect || !stickyForm) return;
// Clone variant select
const stickySelect = mainSelect.cloneNode(true);
stickySelect.classList.add('sticky-variant-select');
// Insert before quantity
const quantity = stickyForm.querySelector('[data-sticky-quantity]');
stickyForm.insertBefore(stickySelect, quantity);
// Sync selections
mainSelect.addEventListener('change', (e) => {
stickySelect.value = e.target.value;
this.updateStickyPrice();
});
stickySelect.addEventListener('change', (e) => {
mainSelect.value = e.target.value;
this.updateStickyPrice();
// Update hidden input
const hiddenInput = stickyForm.querySelector('[name="id"]');
if (hiddenInput) {
hiddenInput.value = e.target.value;
}
});
}
}
Features
- Smart Detection: Shows only when main button is out of view
- Synchronized: Keeps quantity and variants in sync
- AJAX Add to Cart: No page reload when adding items
- Visual Feedback: Success/error states
- Mobile Optimized: Perfect for mobile shoppers
- Product Info: Shows product image and price
- Customizable: Easy to modify appearance and behavior
- Performance: Passive scroll listeners for smooth scrolling
Related Snippets
Shopify Free Shipping Progress Bar
Show customers how much more they need to spend for free shipping
JAVASCRIPTshopifybeginner
javascriptPreview
// Free Shipping Bar Class
class FreeShippingBar {
constructor(options = {}) {
this.threshold = options.threshold || 5000; // Amount in cents
...#shopify#shipping#cart+2
11/25/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 AJAX Add to Cart
Add products to cart without page reload using Shopify AJAX API
JAVASCRIPTshopifyintermediate
javascriptPreview
// Add to cart with AJAX
function addToCart(variantId, quantity = 1) {
const formData = {
items: [{
...#shopify#ajax#cart+2
1/9/2025
View