JAVASCRIPTshopifyintermediate
Shopify Slide-Out Cart Drawer
Create a modern slide-out cart drawer with AJAX functionality
Faisal Yaqoob
November 16, 2025
#shopify#cart#ajax#drawer#javascript
Code
javascript
1 // Cart Drawer Functionality 2 class CartDrawer { 3 constructor() { 4 this.drawer = document.querySelector('[data-cart-drawer]'); 5 this.overlay = document.querySelector('[data-cart-overlay]'); 6 this.closeBtn = document.querySelector('[data-cart-close]'); 7 this.cartCount = document.querySelector('[data-cart-count]'); 8
9 this.init(); 10 } 11
12 init() { 13 // Bind events 14 document.addEventListener('click', (e) => { 15 if (e.target.matches('[data-cart-trigger]')) { 16 e.preventDefault(); 17 this.open(); 18 } 19 }); 20
21 if (this.closeBtn) { 22 this.closeBtn.addEventListener('click', () => this.close()); 23 } 24
25 if (this.overlay) { 26 this.overlay.addEventListener('click', () => this.close()); 27 } 28
29 // Update cart on page load 30 this.updateCart(); 31 } 32
33 open() { 34 this.drawer.classList.add('is-active'); 35 this.overlay.classList.add('is-active'); 36 document.body.style.overflow = 'hidden'; 37 this.updateCart(); 38 } 39
40 close() { 41 this.drawer.classList.remove('is-active'); 42 this.overlay.classList.remove('is-active'); 43 document.body.style.overflow = ''; 44 } 45
46 async updateCart() { 47 try { 48 const response = await fetch('/cart.js'); 49 const cart = await response.json(); 50
51 this.renderCart(cart); 52 this.updateCartCount(cart.item_count); 53 } catch (error) { 54 console.error('Error updating cart:', error); 55 } 56 } 57
58 renderCart(cart) { 59 const cartItems = document.querySelector('[data-cart-items]'); 60 const cartTotal = document.querySelector('[data-cart-total]'); 61 const emptyMessage = document.querySelector('[data-cart-empty]'); 62
63 if (cart.items.length === 0) { 64 cartItems.innerHTML = ''; 65 emptyMessage.style.display = 'block'; 66 return; 67 } 68
69 emptyMessage.style.display = 'none'; 70
71 const itemsHTML = cart.items.map(item => ` 72 <div class="cart-item" data-line-item="${item.key}"> 73 <div class="cart-item__image"> 74 <img src="${item.featured_image.url}" alt="${item.product_title}"> 75 </div> 76 <div class="cart-item__details"> 77 <h4>${item.product_title}</h4> 78 ${item.variant_title ? `<p>${item.variant_title}</p>` : ''} 79 <div class="cart-item__price"> 80 ${this.formatMoney(item.final_line_price)} 81 </div> 82 </div> 83 <div class="cart-item__quantity"> 84 <button class="qty-btn" data-cart-decrease="${item.key}">-</button> 85 <input type="number" value="${item.quantity}" min="0" data-cart-quantity="${item.key}"> 86 <button class="qty-btn" data-cart-increase="${item.key}">+</button> 87 </div> 88 <button class="cart-item__remove" data-cart-remove="${item.key}"> 89 Remove 90 </button> 91 </div> 92 `).join(''); 93
94 cartItems.innerHTML = itemsHTML; 95 cartTotal.textContent = this.formatMoney(cart.total_price); 96
97 this.bindCartEvents(); 98 } 99
100 bindCartEvents() { 101 // Quantity decrease 102 document.querySelectorAll('[data-cart-decrease]').forEach(btn => { 103 btn.addEventListener('click', (e) => { 104 const key = e.target.dataset.cartDecrease; 105 const input = document.querySelector(`[data-cart-quantity="${key}"]`); 106 const newQty = parseInt(input.value) - 1; 107 if (newQty >= 0) { 108 this.updateLineItem(key, newQty); 109 } 110 }); 111 }); 112
113 // Quantity increase 114 document.querySelectorAll('[data-cart-increase]').forEach(btn => { 115 btn.addEventListener('click', (e) => { 116 const key = e.target.dataset.cartIncrease; 117 const input = document.querySelector(`[data-cart-quantity="${key}"]`); 118 const newQty = parseInt(input.value) + 1; 119 this.updateLineItem(key, newQty); 120 }); 121 }); 122
123 // Direct quantity input 124 document.querySelectorAll('[data-cart-quantity]').forEach(input => { 125 input.addEventListener('change', (e) => { 126 const key = e.target.dataset.cartQuantity; 127 const newQty = parseInt(e.target.value); 128 this.updateLineItem(key, newQty); 129 }); 130 }); 131
132 // Remove item 133 document.querySelectorAll('[data-cart-remove]').forEach(btn => { 134 btn.addEventListener('click', (e) => { 135 const key = e.target.dataset.cartRemove; 136 this.updateLineItem(key, 0); 137 }); 138 }); 139 } 140
141 async updateLineItem(key, quantity) { 142 try { 143 const response = await fetch('/cart/change.js', { 144 method: 'POST', 145 headers: { 146 'Content-Type': 'application/json', 147 }, 148 body: JSON.stringify({ 149 id: key, 150 quantity: quantity 151 }) 152 }); 153
154 const cart = await response.json(); 155 this.renderCart(cart); 156 this.updateCartCount(cart.item_count); 157 } catch (error) { 158 console.error('Error updating cart:', error); 159 } 160 } 161
162 updateCartCount(count) { 163 if (this.cartCount) { 164 this.cartCount.textContent = count; 165 this.cartCount.style.display = count > 0 ? 'flex' : 'none'; 166 } 167 } 168
169 formatMoney(cents) { 170 return '$' + (cents / 100).toFixed(2); 171 } 172 } 173
174 // Initialize 175 document.addEventListener('DOMContentLoaded', () => { 176 new CartDrawer(); 177 });
Shopify Slide-Out Cart Drawer
Create a modern slide-out cart drawer that opens from the side with smooth animations and updates in real-time without page reloads.
// Cart Drawer Functionality
class CartDrawer {
constructor() {
this.drawer = document.querySelector('[data-cart-drawer]');
this.overlay = document.querySelector('[data-cart-overlay]');
this.closeBtn = document.querySelector('[data-cart-close]');
this.cartCount = document.querySelector('[data-cart-count]');
this.init();
}
init() {
// Bind events
document.addEventListener('click', (e) => {
if (e.target.matches('[data-cart-trigger]')) {
e.preventDefault();
this.open();
}
});
if (this.closeBtn) {
this.closeBtn.addEventListener('click', () => this.close());
}
if (this.overlay) {
this.overlay.addEventListener('click', () => this.close());
}
// Update cart on page load
this.updateCart();
}
open() {
this.drawer.classList.add('is-active');
this.overlay.classList.add('is-active');
document.body.style.overflow = 'hidden';
this.updateCart();
}
close() {
this.drawer.classList.remove('is-active');
this.overlay.classList.remove('is-active');
document.body.style.overflow = '';
}
async updateCart() {
try {
const response = await fetch('/cart.js');
const cart = await response.json();
this.renderCart(cart);
this.updateCartCount(cart.item_count);
} catch (error) {
console.error('Error updating cart:', error);
}
}
renderCart(cart) {
const cartItems = document.querySelector('[data-cart-items]');
const cartTotal = document.querySelector('[data-cart-total]');
const emptyMessage = document.querySelector('[data-cart-empty]');
if (cart.items.length === 0) {
cartItems.innerHTML = '';
emptyMessage.style.display = 'block';
return;
}
emptyMessage.style.display = 'none';
const itemsHTML = cart.items.map(item => `
<div class="cart-item" data-line-item="${item.key}">
<div class="cart-item__image">
<img src="${item.featured_image.url}" alt="${item.product_title}">
</div>
<div class="cart-item__details">
<h4>${item.product_title}</h4>
${item.variant_title ? `<p>${item.variant_title}</p>` : ''}
<div class="cart-item__price">
${this.formatMoney(item.final_line_price)}
</div>
</div>
<div class="cart-item__quantity">
<button class="qty-btn" data-cart-decrease="${item.key}">-</button>
<input type="number" value="${item.quantity}" min="0" data-cart-quantity="${item.key}">
<button class="qty-btn" data-cart-increase="${item.key}">+</button>
</div>
<button class="cart-item__remove" data-cart-remove="${item.key}">
Remove
</button>
</div>
`).join('');
cartItems.innerHTML = itemsHTML;
cartTotal.textContent = this.formatMoney(cart.total_price);
this.bindCartEvents();
}
bindCartEvents() {
// Quantity decrease
document.querySelectorAll('[data-cart-decrease]').forEach(btn => {
btn.addEventListener('click', (e) => {
const key = e.target.dataset.cartDecrease;
const input = document.querySelector(`[data-cart-quantity="${key}"]`);
const newQty = parseInt(input.value) - 1;
if (newQty >= 0) {
this.updateLineItem(key, newQty);
}
});
});
// Quantity increase
document.querySelectorAll('[data-cart-increase]').forEach(btn => {
btn.addEventListener('click', (e) => {
const key = e.target.dataset.cartIncrease;
const input = document.querySelector(`[data-cart-quantity="${key}"]`);
const newQty = parseInt(input.value) + 1;
this.updateLineItem(key, newQty);
});
});
// Direct quantity input
document.querySelectorAll('[data-cart-quantity]').forEach(input => {
input.addEventListener('change', (e) => {
const key = e.target.dataset.cartQuantity;
const newQty = parseInt(e.target.value);
this.updateLineItem(key, newQty);
});
});
// Remove item
document.querySelectorAll('[data-cart-remove]').forEach(btn => {
btn.addEventListener('click', (e) => {
const key = e.target.dataset.cartRemove;
this.updateLineItem(key, 0);
});
});
}
async updateLineItem(key, quantity) {
try {
const response = await fetch('/cart/change.js', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: key,
quantity: quantity
})
});
const cart = await response.json();
this.renderCart(cart);
this.updateCartCount(cart.item_count);
} catch (error) {
console.error('Error updating cart:', error);
}
}
updateCartCount(count) {
if (this.cartCount) {
this.cartCount.textContent = count;
this.cartCount.style.display = count > 0 ? 'flex' : 'none';
}
}
formatMoney(cents) {
return '$' + (cents / 100).toFixed(2);
}
}
// Initialize
document.addEventListener('DOMContentLoaded', () => {
new CartDrawer();
});
HTML Structure (Liquid)
<!-- Cart Drawer -->
<div class="cart-drawer" data-cart-drawer>
<div class="cart-drawer__header">
<h2>Your Cart</h2>
<button class="cart-drawer__close" data-cart-close>
<svg><!-- Close icon --></svg>
</button>
</div>
<div class="cart-drawer__body">
<div class="cart-items" data-cart-items>
<!-- Cart items will be rendered here -->
</div>
<div class="cart-empty" data-cart-empty style="display: none;">
<p>Your cart is empty</p>
<a href="/collections/all" class="btn">Continue Shopping</a>
</div>
</div>
<div class="cart-drawer__footer">
<div class="cart-total">
<span>Total:</span>
<span data-cart-total>$0.00</span>
</div>
<a href="/checkout" class="btn btn--checkout">Checkout</a>
</div>
</div>
<!-- Overlay -->
<div class="cart-overlay" data-cart-overlay></div>
<!-- Cart Trigger -->
<a href="/cart" data-cart-trigger>
Cart (<span data-cart-count>{{ cart.item_count }}</span>)
</a>
CSS Styling
.cart-drawer {
position: fixed;
top: 0;
right: -400px;
width: 400px;
height: 100vh;
background: #fff;
box-shadow: -2px 0 10px rgba(0,0,0,0.1);
transition: right 0.3s ease;
z-index: 1001;
display: flex;
flex-direction: column;
}
.cart-drawer.is-active {
right: 0;
}
.cart-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
z-index: 1000;
}
.cart-overlay.is-active {
opacity: 1;
visibility: visible;
}
.cart-drawer__header {
padding: 20px;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
}
.cart-drawer__body {
flex: 1;
overflow-y: auto;
padding: 20px;
}
.cart-item {
display: grid;
grid-template-columns: 80px 1fr auto;
gap: 15px;
padding: 15px 0;
border-bottom: 1px solid #eee;
}
.cart-item__image img {
width: 100%;
height: auto;
object-fit: cover;
}
.cart-item__quantity {
display: flex;
align-items: center;
gap: 10px;
}
.qty-btn {
width: 30px;
height: 30px;
border: 1px solid #ddd;
background: #fff;
cursor: pointer;
}
.cart-drawer__footer {
padding: 20px;
border-top: 1px solid #eee;
}
.cart-total {
display: flex;
justify-content: space-between;
font-size: 18px;
font-weight: bold;
margin-bottom: 15px;
}
.btn--checkout {
width: 100%;
padding: 15px;
background: #000;
color: #fff;
text-align: center;
text-decoration: none;
display: block;
}
Features
- Slide-Out Animation: Smooth drawer animation
- Real-Time Updates: AJAX cart updates without reload
- Quantity Controls: Increase/decrease quantities
- Remove Items: Quick item removal
- Cart Count Badge: Dynamic cart count display
- Responsive: Works on all devices
- Accessible: Keyboard and screen reader friendly
Related Snippets
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
Shopify AJAX Collection Filters
Add dynamic filtering to collection pages without page reloads
JAVASCRIPTshopifyadvanced
javascriptPreview
// Collection Filters Class
class CollectionFilters {
constructor(options = {}) {
this.container = document.querySelector(options.container || '[data-collection-container]');
...#shopify#filters#ajax+2
11/27/2025
View
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