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-item

v0.4.2

Published

Cart item web component.

Readme

Cart Item Web Component

A professional, highly-customizable Web Component for creating smooth cart item interactions in e-commerce applications. Features elegant processing states, dramatic destruction animations, and seamless integration with any cart management system.

Live Demo

Features

  • Smooth state transitions - Processing and destroying animations
  • 🎬 Cinematic destruction - Height collapse with blur, scale, and desaturation effects
  • 🎯 Modern 3-dot loader - Sleek bouncing animation replaces outdated spinners
  • 🎛️ 16+ CSS custom properties - Complete visual customization
  • 📡 Event-driven architecture - Clean separation between UI and logic
  • 🎲 Quantity modifier integration - Built-in support for @magic-spells/quantity-modifier
  • 🔧 Minimal dependencies - Only includes quantity-modifier for enhanced UX
  • Lightweight and performant - Optimized animations and memory management
  • 📱 Framework agnostic - Pure Web Components work with any framework
  • 🛒 Shopify-ready - Designed for real-world e-commerce applications
  • 🎨 Template system - Client-side rendering from Shopify cart JSON

Installation

npm install @magic-spells/cart-item
// Import the component (includes quantity-modifier automatically)
import '@magic-spells/cart-item';
import '@magic-spells/cart-item/css/min'; // Include styles

Or include directly in your HTML:

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

Usage

Server-Side Rendered (Pre-rendered HTML)

<cart-item key="shopify-line-item-123">
	<cart-item-content>
		<div class="product-image">
			<img src="product.jpg" alt="Product" />
		</div>
		<div class="product-details">
			<h4>Awesome T-Shirt</h4>
			<div class="price">$29.99</div>
			<div class="quantity">
				<label>Qty:</label>
				<quantity-modifier value="1" min="1" max="99"></quantity-modifier>
			</div>
		</div>
		<div class="actions">
			<button data-action-remove-item>Remove</button>
			<div class="total">$29.99</div>
		</div>
	</cart-item-content>

	<cart-item-processing>
		<div class="cart-item-loader"></div>
	</cart-item-processing>
</cart-item>

Client-Side Rendered (Template-based)

// Set up template once
CartItem.setTemplate((item) => {
	return `
    <div class="product-image">
      <img src="${item.featured_image}" alt="${item.product_title}">
    </div>
    <div class="product-details">
      <h4>${item.product_title}</h4>
      <div class="price">$${(item.line_price / 100).toFixed(2)}</div>
      <div class="quantity">
        <label>Qty:</label>
        <quantity-modifier value="${item.quantity}" min="1" max="99"></quantity-modifier>
      </div>
    </div>
    <div class="actions">
      <button data-action-remove-item>Remove</button>
      <div class="total">$${((item.line_price * item.quantity) / 100).toFixed(2)}</div>
    </div>
  `;
});

// Optionally customize the processing overlay (defaults to modern 3-dot loader)
CartItem.setProcessingTemplate(() => {
	return `
    <div class="custom-loader"></div>
    <span>Processing...</span>
  `;
});

// Create new cart item from Shopify cart data
const cartItem = new CartItem(shopifyItemData);
document.querySelector('#cart').appendChild(cartItem);

// Create new cart item with appearing animation
const animatedCartItem = CartItem.createAnimated(shopifyItemData);
document.querySelector('#cart').appendChild(animatedCartItem);

How It Works

The cart item component manages three distinct states with smooth animations:

  • Ready: Normal state with full interactivity
  • Processing: Slightly scaled and blurred content with processing overlay visible
  • Destroying: Dramatic visual effects (scale, blur, desaturate) while height animates to zero

The component emits events that bubble up to parent components (like cart-panel) for centralized cart management, while handling all visual states and animations internally.

Configuration

Attributes

| Attribute | Description | Required | | --------- | ---------------------------------------------------------------------- | -------- | | key | Unique identifier for the cart item (Shopify line item key) | Yes | | state | Current visual state: ready, processing, destroying, appearing | No |

Required HTML Structure

| Element | Description | Required | | ------------------------ | ------------------------------------------------------------------------------ | -------- | | <cart-item> | Main container with key attribute | Yes | | <cart-item-content> | Content area (product info, buttons) - auto-generated when using templates | Yes | | <cart-item-processing> | Processing overlay (modern 3-dot loader) - auto-generated when using templates | No |

Quantity Inputs

Cart items support two types of quantity controls that both trigger the same cart-item:quantity-change event:

  1. Traditional input: <input data-cart-quantity> - Standard HTML number input
  2. Quantity modifier: <quantity-modifier> - Enhanced component with increment/decrement buttons

The quantity-modifier component provides better UX with:

  • Increment/decrement buttons for easier mobile interaction
  • Built-in min/max validation
  • Consistent styling and behavior
  • No browser spin buttons (automatically hidden)

Interactive Elements

| Selector | Description | Event Triggered | | --------------------------- | --------------------------- | --------------------------- | | [data-action-remove-item] | Remove button | cart-item:remove | | [data-cart-quantity] | Quantity input field | cart-item:quantity-change | | <quantity-modifier> | Quantity modifier component | cart-item:quantity-change |

Data Content Elements

| Selector | Description | Auto-Updated | | --------------------------- | ----------------------------- | ------------ | | [data-content-line-price] | Line total (quantity × price) | Yes |

These elements are automatically updated when cart data changes via setData() method.

Example:

<!-- Minimal pre-rendered cart item -->
<cart-item key="item-123">
	<cart-item-content>
		<h4>Product Name</h4>
		<quantity-modifier value="1" min="1" max="99"></quantity-modifier>
		<button data-action-remove-item>Remove</button>
		<div data-content-line-price>$29.99</div>
	</cart-item-content>
</cart-item>

<!-- Full-featured pre-rendered cart item -->
<cart-item key="shopify-40123456789" state="ready">
	<cart-item-content>
		<!-- Your product layout here -->
	</cart-item-content>
	<cart-item-processing>
		<div class="cart-item-loader"></div>
	</cart-item-processing>
</cart-item>

<!-- Template-based cart item (JavaScript) -->
<script>
	CartItem.setTemplate(
		(item) => `
    <h4>${item.product_title}</h4>
    <quantity-modifier value="${item.quantity}" min="1" max="99"></quantity-modifier>
    <button data-action-remove-item>Remove</button>
    <div data-content-line-price>$${(item.line_price / 100).toFixed(2)}</div>
  `
	);

	// Optionally set custom processing template
	CartItem.setProcessingTemplate(() => `<div class="custom-loader">Loading...</div>`);

	const newItem = new CartItem(shopifyCartItemData);
	document.body.appendChild(newItem);
</script>

Customization

Styling

The component provides complete styling control through CSS custom properties. Style the content elements however you like:

/* Customize animation timings */
cart-item {
	--cart-item-processing-duration: 300ms;
	--cart-item-destroying-duration: 800ms;
}

/* Customize visual effects */
cart-item {
	--cart-item-processing-scale: 0.95;
	--cart-item-destroying-scale: 0.8;
	--cart-item-destroying-blur: 15px;
	--cart-item-destroying-opacity: 0.1;
}

/* Style your content layout */
cart-item-content {
	display: grid;
	grid-template-columns: auto 1fr auto;
	gap: 1rem;
	padding: 1rem;
	background: white;
	border: 1px solid #e0e0e0;
	border-radius: 8px;
}

/* Custom processing overlay */
cart-item-processing {
	background: rgba(255, 255, 255, 0.95);
	backdrop-filter: blur(4px);
}

CSS Variables & SCSS Variables

The component supports both CSS custom properties and SCSS variables for maximum flexibility:

| CSS Variable | SCSS Variable | Description | Default | | --------------------------------- | -------------------------------- | ------------------------------- | ---------------- | | --cart-item-processing-duration | $cart-item-processing-duration | Processing animation duration | 250ms | | --cart-item-destroying-duration | $cart-item-destroying-duration | Destroying animation duration | 600ms | | --cart-item-appearing-duration | $cart-item-appearing-duration | Appearing animation duration | 400ms | | --cart-item-processing-scale | $cart-item-processing-scale | Scale during processing state | 0.98 | | --cart-item-destroying-scale | $cart-item-destroying-scale | Scale during destroying state | 0.85 | | --cart-item-appearing-scale | $cart-item-appearing-scale | Scale during appearing state | 0.9 | | --cart-item-processing-blur | $cart-item-processing-blur | Blur during processing state | 1px | | --cart-item-destroying-blur | $cart-item-destroying-blur | Blur during destroying state | 10px | | --cart-item-appearing-blur | $cart-item-appearing-blur | Blur during appearing state | 2px | | --cart-item-destroying-opacity | $cart-item-destroying-opacity | Opacity during destroying state | 0.2 | | --cart-item-appearing-opacity | $cart-item-appearing-opacity | Opacity during appearing state | 0.5 | | --cart-item-shadow-color | $cart-item-shadow-color | Processing shadow color | rgba(0,0,0,0.15) | | --cart-item-shadow-color-strong | $cart-item-shadow-color-strong | Destroying shadow color | rgba(0,0,0,0.5) | | --cart-item-destroying-bg | $cart-item-destroying-bg | Destroying background color | rgba(0,0,0,0.1) |

CSS Override Examples:

/* Dramatic destruction effect */
.dramatic-cart {
	--cart-item-destroying-duration: 1200ms;
	--cart-item-destroying-scale: 0.3;
	--cart-item-destroying-blur: 20px;
	--cart-item-destroying-opacity: 0;
	--cart-item-destroying-saturate: 0;
}

/* Subtle processing state */
.subtle-cart {
	--cart-item-processing-scale: 0.99;
	--cart-item-processing-blur: 0.5px;
	--cart-item-processing-duration: 150ms;
}

SCSS Override Examples:

// Override SCSS variables before importing
$cart-item-destroying-duration: 800ms;
$cart-item-appearing-duration: 600ms;
$cart-item-processing-scale: 0.95;
$cart-item-destroying-blur: 15px;

// Import the component styles
@import '@magic-spells/cart-item/scss';

// Or import the CSS and override with CSS custom properties
@import '@magic-spells/cart-item/css';

.my-cart cart-item {
	--cart-item-destroying-duration: 800ms;
	--cart-item-appearing-duration: 600ms;
}

JavaScript API

Static Methods

  • CartItem.setTemplate(templateFn): Set the template function for all cart items
  • CartItem.setProcessingTemplate(templateFn): Set the processing overlay template (optional, defaults to modern 3-dot bouncing loader)
  • CartItem.createAnimated(itemData): Create a new cart item with appearing animation

Instance Methods

  • setState(state): Change the visual state ('ready', 'processing', 'destroying', 'appearing')
  • destroyYourself(): Trigger the destruction animation and remove from DOM
  • setData(itemData): Update the cart item with new Shopify cart data
  • cartKey: Get the cart item's unique identifier (getter)
  • state: Get the current state (getter)
  • itemData: Get the current item data (getter)

Events

The component emits custom events that bubble up for parent components to handle:

cart-item:remove

  • Triggered when a remove button ([data-action-remove]) is clicked
  • event.detail: { cartKey, element }
  • Works with both pre-rendered and template-based items

cart-item:quantity-change

  • Triggered when a quantity input ([data-cart-quantity]) value changes
  • Also triggered when a quantity-modifier component (<quantity-modifier>) value changes
  • event.detail: { cartKey, quantity, element }
  • Works with both pre-rendered and template-based items

Programmatic Control

// Set up template (do this once, before creating items)
CartItem.setTemplate((item) => {
	return `
    <div class="product-info">
      <h4>${item.product_title}</h4>
      <input data-cart-quantity value="${item.quantity}">
      <button data-action-remove-item>Remove</button>
    </div>
  `;
});

// Optionally customize processing overlay (defaults to modern 3-dot loader)
CartItem.setProcessingTemplate(() => {
	return `
    <div class="custom-loader"></div>
    <span>Processing...</span>
  `;
});

// Create from Shopify cart data
const cartItem = new CartItem(shopifyItemData);
document.querySelector('#cart').appendChild(cartItem);

// Create with appearing animation
const animatedCartItem = CartItem.createAnimated(shopifyItemData);
document.querySelector('#cart').appendChild(animatedCartItem);

// Or work with existing pre-rendered items
const existingItem = document.querySelector('cart-item');

// Change states
existingItem.setState('processing'); // Show processing overlay
existingItem.setState('ready'); // Return to normal
existingItem.setState('destroying'); // Apply destruction effects

// Trigger destruction (includes animation + DOM removal)
existingItem.destroyYourself();

// Update with new data
existingItem.setData(updatedShopifyData);

// Get item information
console.log(existingItem.cartKey); // "shopify-line-item-123"
console.log(existingItem.state); // "ready"
console.log(existingItem.itemData); // Original Shopify item data

// Listen for events
document.addEventListener('cart-item:remove', (e) => {
	console.log('Remove requested:', e.detail.cartKey);

	// Set processing state
	e.detail.element.setState('processing');

	// Make AJAX call, then destroy
	fetch('/cart/remove', {
		/* ... */
	}).then(() => e.detail.element.destroyYourself());
});

document.addEventListener('cart-item:quantity-change', (e) => {
	console.log('Quantity changed:', e.detail.quantity);
	// Handle quantity update...
});

Performance & Architecture

The component is optimized for:

  • Smooth animations: CSS transforms and transitions with requestAnimationFrame
  • Memory management: Proper event listener cleanup and duplicate call prevention
  • Separation of concerns: UI/animation vs business logic separation
  • Event delegation: Efficient event handling for dynamic content
  • Hardware acceleration: CSS transforms for smooth visual effects

Integration Examples

Shopify Integration

// Example cart panel integration
class CartPanel {
	constructor() {
		// Set up template for dynamically added items
		CartItem.setTemplate((item) => {
			return `
        <div class="product-image">
          <img src="${item.featured_image}" alt="${item.product_title}">
        </div>
        <div class="product-details">
          <h4>${item.product_title}</h4>
          <p>${item.variant_title || ''}</p>
          <quantity-modifier value="${item.quantity}" min="1" max="99"></quantity-modifier>
        </div>
        <div class="product-actions">
          <button data-action-remove-item>Remove</button>
          <span>$${(item.line_price / 100).toFixed(2)}</span>
        </div>
      `;
		});

		document.addEventListener('cart-item:remove', this.handleRemove.bind(this));
		document.addEventListener('cart-item:quantity-change', this.handleQuantityChange.bind(this));
	}

	async handleRemove(e) {
		const { cartKey, element } = e.detail;

		// Show processing state
		element.setState('processing');

		try {
			// Shopify AJAX API call
			await fetch('/cart/change.js', {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ id: cartKey, quantity: 0 }),
			});

			// Animate out and remove
			element.destroyYourself();
		} catch (error) {
			// Revert to ready state on error
			element.setState('ready');
			console.error('Failed to remove item:', error);
		}
	}

	// Add new item from cart response
	addCartItem(shopifyItem, options = {}) {
		const cartItem = options.animate
			? CartItem.createAnimated(shopifyItem)
			: new CartItem(shopifyItem);
		this.cartContainer.appendChild(cartItem);
	}

	async handleQuantityChange(e) {
		const { cartKey, quantity, element } = e.detail;

		// Optional: Show processing state for quantity changes
		// element.setState('processing');

		try {
			await fetch('/cart/change.js', {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ id: cartKey, quantity }),
			});

			// Update UI with new totals
			this.updateCartTotals();
		} catch (error) {
			console.error('Failed to update quantity:', error);
		}
	}
}

new CartPanel();

Vanilla JavaScript Integration

// Example without any framework
class SimpleCartManager {
	constructor() {
		// Set up template for new items
		CartItem.setTemplate((item) => {
			return `
        <div class="item-info">
          <h4>${item.product_title}</h4>
          <quantity-modifier value="${item.quantity}" min="1" max="99"></quantity-modifier>
          <button data-action-remove-item>Remove</button>
          <span>$${((item.line_price * item.quantity) / 100).toFixed(2)}</span>
        </div>
      `;
		});

		document.addEventListener('cart-item:remove', this.handleRemove.bind(this));
		document.addEventListener('cart-item:quantity-change', this.handleQuantityChange.bind(this));
	}

	handleRemove(e) {
		const { cartKey, element } = e.detail;
		console.log(`Removing item: ${cartKey}`);

		// Show processing state
		element.setState('processing');

		// Simulate API call
		setTimeout(() => {
			element.destroyYourself();
		}, 1000);
	}

	handleQuantityChange(e) {
		const { cartKey, quantity } = e.detail;
		console.log(`Item ${cartKey} quantity changed to ${quantity}`);

		// Update your cart state/UI as needed
		this.updateCartDisplay();
	}

	// Add new item from cart data
	addNewItem(cartItemData, options = {}) {
		const cartItem = options.animate
			? CartItem.createAnimated(cartItemData)
			: new CartItem(cartItemData);
		document.querySelector('#cart-container').appendChild(cartItem);
	}

	updateCartDisplay() {
		// Your cart update logic here
	}
}

// Initialize
new SimpleCartManager();

Browser Support

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

All modern browsers with Web Components support.

License

MIT