npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@magic-spells/cart-panel

v1.0.1

Published

Shopping cart panel web component for Shopify. Manages cart data and AJAX, delegates modal behavior to dialog-panel.

Readme

Cart Panel Web Component

A professional, highly-customizable shopping cart component built with Web Components. Features smooth animations, real-time cart synchronization, and seamless integration with Shopify and other e-commerce platforms.

Live Demo

Features

  • Complete cart management - Handles cart data, AJAX requests, and item rendering
  • Delegates modal to dialog-panel - Works with @magic-spells/dialog-panel for accessible modal behavior
  • Real-time sync - Automatic cart updates via /cart.json and /cart/change.json APIs
  • Event-driven architecture - Rich event system with custom event emitter
  • Smooth animations - CSS transitions for processing, appearing, and destroying states
  • Highly customizable - CSS custom properties and template system
  • Framework agnostic - Pure Web Components work with any framework
  • Shopify-ready - Built specifically for Shopify cart integrations

Installation

npm install @magic-spells/cart-panel
// Import the component (includes cart-item automatically)
import '@magic-spells/cart-panel';

// Import styles (includes cart-item styles automatically)
import '@magic-spells/cart-panel/css';

Or include directly in your HTML:

<script src="https://unpkg.com/@magic-spells/cart-panel"></script>
<link rel="stylesheet" href="https://unpkg.com/@magic-spells/cart-panel/dist/cart-panel.css" />

Usage

The cart-panel component delegates modal behavior to a <dialog-panel> ancestor. It finds its nearest <dialog-panel> and calls show()/hide() on it.

<!-- Cart with dialog-panel wrapper -->
<dialog-panel id="cart-dialog">
  <dialog aria-labelledby="cart-title">
    <cart-panel manual>
      <div class="cart-header">
        <h2 id="cart-title">Shopping Cart</h2>
        <button aria-label="Close cart" class="close-button" data-action-hide-cart>
          &times;
        </button>
      </div>
      <div class="cart-body">
        <!-- Cart has items section -->
        <div data-cart-has-items>
          <div class="cart-items" data-content-cart-items>
            <!-- Cart items rendered dynamically -->
          </div>
        </div>

        <!-- Cart is empty section -->
        <div data-cart-is-empty>
          <div class="empty-cart">
            <p>Your cart is empty</p>
            <p>Add some items to get started!</p>
          </div>
        </div>
      </div>
      <div class="cart-footer">
        <div class="cart-total">
          Subtotal: <span data-content-cart-subtotal>$0.00</span>
        </div>
        <button class="checkout-button">Proceed to Checkout</button>
      </div>
    </cart-panel>
  </dialog>
</dialog-panel>

<!-- Trigger button -->
<button onclick="document.querySelector('cart-panel').show()">
  Open Cart
</button>

How It Works

The cart panel architecture consists of:

  • cart-panel: Main component managing cart data, AJAX requests, and rendering
  • cart-item: Individual cart item with state management and animations
  • cart-item-content: Content wrapper inside cart-item
  • cart-item-processing: Processing overlay with loader

The component automatically handles:

  • Fetching cart data from /cart.json on show
  • Updating cart items via /cart/change.json API calls
  • Smart rendering with add/update/remove animations
  • Filtering out cart items with _hide_in_cart property from display and calculations
  • Emitting events for cart updates and state changes

Key Architecture Decisions

  1. Delegates modal to dialog-panel: CartPanel finds its nearest <dialog-panel> ancestor and calls show()/hide() on it. No modal management code in cart-panel.

  2. Native dialog features: Focus trap, escape key, backdrop click are all handled by <dialog-panel> which wraps native <dialog>.

  3. Event-driven items: CartItem emits cart-item:remove and cart-item:quantity-change events that bubble up to CartPanel.

Configuration

CartPanel Attributes

| Attribute | Type | Description | | --------- | ------- | ------------------------------------------------------ | | manual | Boolean | Skip auto-refresh on connect, require explicit refreshCart() | | state | String | Reflected attribute: 'has-items' or 'empty' |

Required HTML Structure

| Selector | Description | Required | | --------------------------- | ---------------------------------------- | -------- | | [data-content-cart-items] | Container where cart-item elements render | Yes | | [data-cart-has-items] | Section shown when cart has visible items | No | | [data-cart-is-empty] | Section shown when cart is empty | No | | [data-action-hide-cart] | Close buttons (click triggers hide()) | No | | [data-content-cart-count] | Elements updated with visible item count | No | | [data-content-cart-subtotal] | Elements updated with formatted subtotal | No |

CartItem Child Elements

| Selector | Description | | --------------------------- | ---------------------------------------------- | | [data-action-remove-item] | Remove button (triggers cart-item:remove) | | [data-cart-quantity] | Quantity input field | | [data-content-line-price] | Line price display (auto-formatted) |

JavaScript API

CartPanel Methods

const cartPanel = document.querySelector('cart-panel');

// Dialog Control
cartPanel.show(triggerEl?, cartObj?)  // Open modal and refresh cart
cartPanel.hide()                       // Close modal

// Cart Data
cartPanel.getCart()                    // Fetch from /cart.json
cartPanel.updateCartItem(key, quantity) // POST to /cart/change.json
cartPanel.refreshCart(cartObj?)        // Update display with cart data

// Templates
cartPanel.setCartItemTemplate(name, fn)       // Set template function
cartPanel.setCartItemProcessingTemplate(fn)   // Set processing overlay template

// Event Subscription (chainable)
cartPanel.on(eventName, callback)      // Add event listener
cartPanel.off(eventName, callback)     // Remove event listener

CartPanel Events

| Event | Detail | Description | | ---------------------- | --------------------------------------------------- | ------------------------ | | cart-panel:show | { triggerElement } | When show() called | | cart-panel:hide | {} | When hide() called | | cart-panel:refreshed | { cart } | After cart data refreshed | | cart-panel:updated | { cart } | After item quantity/remove | | cart-panel:data-changed | { calculated_count, calculated_subtotal, ... } | Any cart change |

CartItem Events (bubbled)

| Event | Detail | Description | | ------------------------- | ---------------------------------- | --------------------- | | cart-item:remove | { cartKey, element } | Remove button clicked | | cart-item:quantity-change | { cartKey, quantity, element } | Quantity changed |

CartItem States

| State | Description | | ------------ | ---------------------------------------------- | | ready | Interactive state, content visible | | processing | During AJAX calls, blur/scale effects, loader visible | | destroying | Removal animation (height collapses) | | appearing | Entry animation (height expands from 0) |

CartItem Static Methods

import { CartItem } from '@magic-spells/cart-panel';

// Set template globally
CartItem.setTemplate(name, templateFn)

// Set processing overlay template
CartItem.setProcessingTemplate(templateFn)

// Create with animation
CartItem.createAnimated(itemData, cartData)

Programmatic Control

const cartPanel = document.querySelector('cart-panel');

// Open/close cart
cartPanel.show();
cartPanel.hide();

// Cart data operations
const cartData = await cartPanel.getCart();
const updatedCart = await cartPanel.updateCartItem('item-key', 2);
await cartPanel.refreshCart();

// Event emitter pattern (chainable)
cartPanel
  .on('cart-panel:show', (e) => {
    console.log('Cart opened by:', e.detail.triggerElement);
  })
  .on('cart-panel:data-changed', (e) => {
    console.log('Cart updated:', e.detail);
    // Update header cart count, etc.
  });

// Traditional event listeners also work
cartPanel.addEventListener('cart-item:remove', (e) => {
  console.log('Remove requested:', e.detail.cartKey);
});

Template System

Set up custom templates to control how cart items render:

const cartPanel = document.querySelector('cart-panel');

// Default template
cartPanel.setCartItemTemplate('default', (itemData, cartData) => {
  return `
    <div class="cart-item">
      <img src="${itemData.image}" alt="${itemData.product_title}" />
      <div class="cart-item-info">
        <h4>${itemData.product_title}</h4>
        <div class="variant">${itemData.variant_title || ''}</div>
      </div>
      <quantity-input value="${itemData.quantity}" min="1"></quantity-input>
      <button data-action-remove-item>Remove</button>
      <span data-content-line-price></span>
    </div>
  `;
});

// Custom template for subscriptions
cartPanel.setCartItemTemplate('subscription', (itemData, cartData) => {
  return `
    <div class="subscription-item">
      <div class="recurring-badge">Subscription</div>
      <h4>${itemData.product_title}</h4>
      <div class="price">$${(itemData.price / 100).toFixed(2)}/month</div>
    </div>
  `;
});

// Custom processing overlay
cartPanel.setCartItemProcessingTemplate(() => {
  return `<div class="custom-loader">Updating...</div>`;
});

Customization

CSS Custom Properties

cart-item {
  /* Animation durations */
  --cart-item-processing-duration: 250ms;
  --cart-item-destroying-duration: 600ms;
  --cart-item-appearing-duration: 400ms;

  /* Colors */
  --cart-item-shadow-color: rgba(0, 0, 0, 0.15);
  --cart-item-shadow-color-strong: rgba(0, 0, 0, 0.5);
  --cart-item-destroying-bg: rgba(0, 0, 0, 0.1);

  /* Scale transforms */
  --cart-item-processing-scale: 0.98;
  --cart-item-destroying-scale: 0.85;
  --cart-item-appearing-scale: 0.9;

  /* Blur effects */
  --cart-item-processing-blur: 1px;
  --cart-item-destroying-blur: 10px;
  --cart-item-appearing-blur: 2px;

  /* Opacity and filters */
  --cart-item-destroying-opacity: 0.2;
  --cart-item-appearing-opacity: 0.5;
  --cart-item-destroying-brightness: 0.6;
  --cart-item-destroying-saturate: 0.3;
}

Line Item Properties

The cart-panel supports Shopify line item properties for enhanced functionality:

| Property | Purpose | | --------------------------- | -------------------------------------- | | _hide_in_cart | Hide item from display (still in cart) | | _ignore_price_in_subtotal | Exclude from subtotal calculation | | _cart_template | Use specific template name for rendering | | _group_id | Group items together (bundles) | | _group_role | Role within a group: "parent" or "child" |

Hidden Items (_hide_in_cart)

// Item hidden from display but stays in actual cart
{
  "key": "item-123",
  "properties": {
    "_hide_in_cart": "true"
  }
}

Custom Templates (_cart_template)

// Use subscription template for this item
{
  "key": "subscription-item",
  "properties": {
    "_cart_template": "subscription"
  }
}

Bundle Grouping (_group_id / _group_role)

// Bundle: Parent shows, children hidden
{
  "items": [
    {
      "key": "bundle-parent",
      "properties": {
        "_group_id": "Q6RT1B48",
        "_group_role": "parent",
        "_cart_template": "bundle"
      }
    },
    {
      "key": "bundle-child-1",
      "properties": {
        "_group_id": "Q6RT1B48",
        "_group_role": "child",
        "_hide_in_cart": "true"
      }
    }
  ]
}

Subtotal Exclusion (_ignore_price_in_subtotal)

// Gift item excluded from subtotal calculation
{
  "key": "gift-item",
  "properties": {
    "_ignore_price_in_subtotal": "true"
  }
}

Shopify Integration

<!-- Cart with dialog-panel wrapper -->
<dialog-panel id="cart-dialog">
  <dialog aria-labelledby="cart-title">
    <cart-panel>
      <div class="cart-header">
        <h2 id="cart-title">Your Cart</h2>
        <button aria-label="Close cart" data-action-hide-cart>&times;</button>
      </div>
      <div class="cart-body">
        <div data-cart-has-items>
          <div class="cart-items" data-content-cart-items></div>
        </div>
        <div data-cart-is-empty>
          <p>Your cart is empty</p>
        </div>
      </div>
      <footer class="cart-footer">
        <div class="cart-summary">
          <span data-content-cart-count></span> items |
          <span data-content-cart-subtotal></span>
        </div>
        <a href="/checkout" class="checkout-button">Checkout</a>
      </footer>
    </cart-panel>
  </dialog>
</dialog-panel>

<script>
  const cartPanel = document.querySelector('cart-panel');

  // Update header cart count on changes
  cartPanel.on('cart-panel:data-changed', (e) => {
    document.querySelector('.header-cart-count').textContent =
      e.detail.calculated_count;
  });
</script>

Dependencies

  • @magic-spells/event-emitter - Event system (bundled)
  • @magic-spells/dialog-panel - Modal behavior (peer dependency, optional)
  • @magic-spells/quantity-input - Quantity controls (optional, for templates)

Browser Support

  • Chrome 54+
  • Firefox 63+
  • Safari 10.1+
  • Edge 79+

All modern browsers with Web Components support.

License

MIT