CartDrawer Web Component
assets/component-cart-drawer.js registers <cart-drawer>, the element that owns the cart drawer's open/close state (no Alpine.js) and re-renders its contents from the section HTML that cart.js bundles into every cart mutation.
Source: assets/component-cart-drawer.js
What It Does
- Extends
HTMLElementto define<cart-drawer>. - Owns open/close state by toggling its own
cart-openclass — no external state manager required. - Intercepts clicks on
#header-cart-bubble(a plain link to/cart, kept as a no-JS fallback) and toggles the drawer instead. - Closes when
.drawer-overlayor.cart-drawer__closeis clicked; toggles thenote-openclass on the cart note label. - Subscribes to
cart:changeondocumentand re-renders its.drawer__wrapperfromevent.detail.sections. - Opens automatically when the event's
actionis'add'.
API Overview
| Method / Lifecycle | Purpose |
|---|---|
constructor() | Binds onCartChange and onDocumentClick to the instance. |
connectedCallback() | Adds the cart:change and document click listeners plus inner clicks. |
disconnectedCallback() | Removes the document-level listeners to avoid leaks when detached. |
sectionId (getter) | Reads the section id from the closest .shopify-section ancestor. |
open() / close() / toggle() | Add / remove / toggle the cart-open class on the element. |
onDocumentClick(e) | Toggles the drawer when #header-cart-bubble is clicked. |
onInnerClick(e) | Handles overlay/close clicks and the cart note label toggle. |
onCartChange(e) | Re-renders from section HTML; opens the drawer after adds. |
render(html, action) | Swaps .drawer__wrapper content, preserving scroll position. |
Detailed Methods
sectionId
js
get sectionId() {
return this.closest('.shopify-section')?.id.replace('shopify-section-', '');
}- Nothing is hardcoded —
cart.jsuses the same lookup to know which sections to request from the Section Rendering API.
onDocumentClick(event)
js
onDocumentClick(event) {
const bubble = event.target.closest('#header-cart-bubble');
if (bubble) {
event.preventDefault();
this.toggle();
}
}- The header cart bubble is a plain
<a href="{{ routes.cart_url }}">; with JS active the drawer intercepts it, without JS the link still navigates to the cart page.
onInnerClick(event)
js
onInnerClick(event) {
if (event.target.closest('.drawer-overlay, .cart-drawer__close')) {
this.close();
return;
}
const noteLabel = event.target.closest('.cart-note label');
if (noteLabel) noteLabel.classList.toggle('note-open');
}- One delegated listener covers the overlay, the close button, and the cart note label — all of which are re-rendered DOM, so delegation keeps them working after updates.
onCartChange(event)
js
onCartChange(event) {
const html = event.detail.sections?.[this.sectionId];
if (html) this.render(html, event.detail.action);
if (event.detail.action === 'add') this.open();
}event.detail.sectionsis the Section Rendering API HTML bundled bycart.jsinto every mutation (add,change,update,refresh).- After a successful add-to-cart the drawer opens automatically.
render(html, action)
js
render(html, action) {
const next = new DOMParser().parseFromString(html, 'text/html').querySelector('#cart-drawer .drawer__wrapper');
const current = this.querySelector('.drawer__wrapper');
if (!next || !current) return;
const scrollTop = current.querySelector('.cart-items')?.scrollTop || 0;
current.innerHTML = next.innerHTML;
const items = current.querySelector('.cart-items');
if (items) items.scrollTop = action === 'add' ? 0 : scrollTop;
}- Preserves the
.cart-itemsscroll position across quantity changes; scrolls back to the top after an add so the shopper sees the new item.
Registration
js
if (!customElements.get('cart-drawer')) {
customElements.define('cart-drawer', CartDrawer);
}- The guard prevents redefinition during hot reloads or multiple imports.
Integration with the Cart Engine
The drawer is a consumer of assets/cart.js (loaded globally from layout/theme.liquid):
js
document.addEventListener('cart:change', this.onCartChange);cart:changedetail:{ action, payload, response, cart, previousCart, sections, source }.- Quantity steppers, remove links, and the cart note inside the drawer need no wiring here —
cart.jshandles them via document-level event delegation (a[href*="/cart/change"],.cart-quantity input,textarea[name="note"]). - While requests are in flight,
cart.jssetshtml.cart-busyandcart.cssdims.cart-items.
Implementation Notes
- Only one
<cart-drawer>should exist per page; multiple instances could open conflicting UI states. - The drawer must live inside a
.shopify-sectionwrapper sosectionId(and therefore section re-rendering) works. - To open or close the drawer programmatically, call the element's methods directly, e.g.
document.querySelector('cart-drawer')?.open(). - Wrapper uses
color-\{\{ settings.cart_color_scheme \}\}for theme integration.