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/gift-with-purchase

v1.0.0

Published

Gift with purchase web component for e-commerce threshold promotions.

Downloads

16

Readme

Gift with Purchase

A powerful, e-commerce web component for automatic gift-with-purchase threshold promotions. Seamlessly integrates with Shopify and automatically manages gift items in the cart based on spending thresholds.

Live Demo

Features

  • 🎁 Automatic Gift Management - Adds/removes gifts based on cart thresholds using smart pricing logic
  • 🛒 Shopify Integration - Built-in Cart API support with proper line item properties
  • 📱 Cart Panel Sync - Automatically syncs with cart-panel components using calculated_subtotal
  • 🎨 Highly Customizable - CSS custom properties and swappable content elements
  • Event-Driven - Custom events for gift addition, removal, and errors
  • 🔧 Flexible Content - Data attributes for dynamic image, title, and variant updates
  • 📱 Responsive - Mobile-optimized with responsive design

Installation

npm install @magic-spells/gift-with-purchase

Basic Usage

<gift-with-purchase
	threshold="75.00"
	current="45.00"
	variant-id="12345678"
	money-format="${{amount}}"
	message-above="🎉 Congratulations! You've qualified for your FREE gift!"
	message-below="Add [amount] more to unlock your free gift! 🎁">
	<div class="gwp-product">
		<img src="gift-image.jpg" alt="Free Gift" />
		<div class="gwp-content">
			<h4 class="gwp-title">Free Sample Set</h4>
			<p class="gwp-variant">Travel Size Collection</p>
		</div>
	</div>
	<p data-content-gwp-message class="text-sm font-medium"></p>
</gift-with-purchase>
@import '@magic-spells/gift-with-purchase/css';

Cart Integration

The component automatically listens for cart data changes when placed inside a <cart-panel> component from the @magic-spells/cart-panel package:

<cart-panel>
	<gift-with-purchase
		threshold="75.00"
		variant-id="12345678"
		money-format={{ shop.money_format | json }}
		message-above="🎉 Congratulations! You've qualified for your FREE gift!"
		message-below="Add [amount] more to unlock your free gift! 🎁">
		<!-- Gift content -->
	</gift-with-purchase>
</cart-panel>

When the cart-panel emits a cart-panel:data-changed event (typically from Shopify cart updates), the gift component will automatically:

  • Update the current cart amount using calculated_subtotal for accurate threshold calculation
  • Check if the threshold is met (excludes other gifts and honors pricing exclusions)
  • Add the gift to cart if threshold is reached
  • Remove the gift if cart falls below threshold

Smart Pricing Logic

The component uses intelligent threshold calculation:

  • Uses calculated_subtotal from cart-panel which properly handles item exclusions
  • Excludes gifts: Other gifts with purchase won't count toward this threshold
  • Includes bundle items: Hidden bundle components that should count are included
  • Multi-currency support: Automatically converts threshold using Shopify.currency.rate for stores with multiple currencies
  • Requires cart-panel: The component requires calculated_subtotal in cart events to function

JavaScript API

const gwp = document.querySelector('gift-with-purchase');

// Setters - update state programmatically
gwp.setCurrentAmount(85.5);
gwp.setThreshold(100.0);
gwp.setVariantId('87654321');

// Get full state object
const state = gwp.getState();
console.log(state.isActive, state.isAdded, state.remainingAmount);

// Readonly getters - access individual properties
console.log(gwp.currentAmount);     // number - current cart amount
console.log(gwp.threshold);         // number - threshold amount
console.log(gwp.variantId);         // string - gift variant ID
console.log(gwp.isActive);          // boolean - threshold met
console.log(gwp.isAdded);           // boolean - gift in cart
console.log(gwp.promoEnded);        // boolean - promo has ended
console.log(gwp.productAvailable);  // boolean - gift product available
console.log(gwp.isDisabled);        // boolean - promo ended OR product unavailable

Attributes

| Attribute | Description | Example | | --------------- | -------------------------------------------------------------------- | ------------------------------------------------------------ | | threshold | Spending threshold to unlock the gift (auto-converts for multi-currency) | "75.00" | | current | Current cart amount (typically set automatically via cart-panel) | "45.00" | | variant-id | Shopify variant ID for the gift product | "12345678" | | promo-ended | Disables the promo and hides the component | "true" | | product-available | Whether the gift product is available (disables if false) | "true" | | message-above | Message shown when threshold is met | "🎉 Congratulations! You've qualified for your FREE gift!" | | message-below | Message shown below threshold; use [amount] for remaining amount | "Add [amount] more to unlock your free gift! 🎁" | | money-format | Shopify-style money format for currency display | "${{amount}}" |

The [amount] Placeholder

In the message-below attribute, use [amount] (with square brackets) to insert the remaining amount needed to reach the threshold. The component automatically:

  • Calculates the remaining amount (threshold - currentAmount)
  • Converts to the customer's currency using Shopify.currency.rate
  • Formats using your money-format attribute
<!-- Example: If threshold is $75 and cart is $45, displays "Add $30 more..." -->
<gift-with-purchase
	threshold="75.00"
	money-format="${{amount}}"
	message-below="Add [amount] more to unlock your free gift!">
</gift-with-purchase>

Why square brackets? We use [amount] instead of {{amount}} to avoid conflicts with Shopify Liquid templates and JavaScript template literals.

Multi-Currency Support

The component automatically handles currency conversion for multi-currency Shopify stores:

  • Set threshold in your store's base currency (e.g., USD)
  • The component reads Shopify.currency.rate from the browser
  • Threshold is automatically converted to the customer's selected currency
  • The [amount] placeholder displays in the converted currency

Message Element

The component requires a message element to display threshold messages:

<gift-with-purchase threshold="75.00" variant-id="12345678">
	<!-- Your gift content with any styling -->
	<div class="gift-content">
		<h4 class="font-bold">Free Gift</h4>
		<p class="text-gray-600">Sample Description</p>
	</div>

	<!-- Required: Element where messages will be injected -->
	<p data-content-gwp-message class="bg-green-100 p-2 rounded"></p>
</gift-with-purchase>

Currency Formatting

Use the money-format attribute to format amounts with the correct currency symbol and formatting:

<gift-with-purchase
	threshold="75.00"
	variant-id="12345678"
	money-format="${{amount}}"
	message-below="Add [amount] more to unlock your free gift!">
	<!-- Gift content -->
</gift-with-purchase>

In Shopify themes, you can pass the shop's money format using the json filter:

<gift-with-purchase
	threshold="75.00"
	variant-id="12345678"
	money-format={{ shop.money_format | json }}
	message-below="Add [amount] more to unlock your free gift!">
	<!-- Gift content -->
</gift-with-purchase>

Available Shopify money formats:

  • shop.money_format - Basic format (e.g., ${{amount}})
  • shop.money_with_currency_format - With currency code (e.g., ${{amount}} USD)

Supported Format Placeholders

| Placeholder | Output | Example | | ----------- | ------ | ------- | | {{amount}} | Amount with decimals | 19.99 | | {{amount_no_decimals}} | Rounded whole number | 20 | | {{amount_with_comma_separator}} | Comma as decimal | 19,99 | | {{amount_no_decimals_with_comma_separator}} | Whole with comma thousands | 1,000 |

Examples by Currency

<!-- USD -->
money-format="${{amount}}"         <!-- $30.00 -->

<!-- EUR -->
money-format="€{{amount}}"         <!-- €30.00 -->
money-format="{{amount}} €"        <!-- 30.00 € -->

<!-- GBP -->
money-format="£{{amount}}"         <!-- £30.00 -->

<!-- JPY (no decimals) -->
money-format="¥{{amount_no_decimals}}"  <!-- ¥3000 -->

Events

The component emits custom events for integration:

// Gift successfully added to cart
gwp.addEventListener('gwp:added', (event) => {
	console.log('Gift added:', event.detail.variantId);
});

// Gift successfully removed from cart
gwp.addEventListener('gwp:removed', (event) => {
	console.log('Gift removed:', event.detail.variantId);
});

// Error occurred during add/remove
gwp.addEventListener('gwp:error', (event) => {
	console.error('Error:', event.detail.error);
	console.log('Action:', event.detail.action); // 'add' or 'remove'
});

Customization

Use CSS custom properties to customize the appearance:

gift-with-purchase {
	--gwp-border-radius: 12px;
	--gwp-padding: 1.5rem;
	--gwp-bg-active: #e8f5e8;
	--gwp-border-active: #28a745;
	--gwp-text-active: #155724;
	--gwp-image-size: 80px;
}

Available CSS Custom Properties

| Property | Description | Default | | --------------------- | ---------------------- | --------- | | --gwp-border-radius | Border radius | 8px | | --gwp-padding | Internal padding | 1rem | | --gwp-bg-active | Background when active | #e8f5e8 | | --gwp-bg-added | Background when added | #d4edda | | --gwp-border-active | Border when active | #28a745 | | --gwp-border-added | Border when added | #155724 | | --gwp-text-active | Text color when active | #155724 | | --gwp-text-added | Text color when added | #155724 | | --gwp-gap | Gap between elements | 1rem |

Component States

The component automatically applies a state attribute based on its current condition:

  • state="active" - Threshold met, gift available to add
  • state="added" - Gift successfully added to cart
  • state="inactive" - Below threshold (component idle)
  • state="ended" - Promo ended (component hidden)
  • state="disabled" - Gift unavailable (component hidden)

Note: ended and disabled both hide the component while still removing any existing gift items.

Shopify Integration Details

Cart API Calls

The component uses Shopify's Cart API endpoints:

  • POST /cart/add.js - Adds the gift to cart
  • GET /cart.js - Gets current cart state
  • POST /cart/change.js - Removes gift from cart

Line Item Properties

When adding gifts to the cart, the component automatically sets these properties for proper cart integration:

{
  id: variantId,
  quantity: 1,
  properties: {
    _gwp_item: "true",
    _hide_in_cart: "true",
    _ignore_price_in_subtotal: "true"
  }
}

Property Functions:

  • _gwp_item: "true" - Identifies gift items in cart templates and for removal logic
  • _hide_in_cart: "true" - Hides gifts from cart display (handled by @magic-spells/cart-panel)
  • _ignore_price_in_subtotal: "true" - Excludes gift price from subtotal calculations and threshold checks
  • Automatic management - These properties ensure gifts don't interfere with other promotions or cart totals

Cart Template Integration

In your Shopify cart template, you can identify and handle gift items:

{% for item in cart.items %}
  {% if item.properties._gwp_item == 'true' %}
    <!-- This is a gift item -->
    <div class="cart-gift-item">
      <span class="gift-badge">FREE GIFT</span>
      {{ item.product.title }}
    </div>
  {% else %}
    <!-- Regular cart item -->
  {% endif %}
{% endfor %}

Advanced Usage

Multiple Thresholds

<!-- Low tier gift -->
<gift-with-purchase
	threshold="50.00"
	variant-id="111111"
	message-above="🎉 You've unlocked a free tote bag!"
	message-below="Add [amount] more for a free tote bag! 👜">
	<div class="gwp-product">
		<img src="tote-bag.jpg" alt="Free Tote Bag" />
		<h4 class="gwp-title">Free Tote Bag</h4>
	</div>
	<p data-content-gwp-message class="message-low-tier"></p>
</gift-with-purchase>

<!-- High tier gift -->
<gift-with-purchase
	threshold="100.00"
	variant-id="222222"
	message-above="✨ Amazing! You've earned our premium gift set!"
	message-below="Spend [amount] more for our exclusive premium gift set! ✨">
	<div class="gwp-product">
		<img src="gift-set.jpg" alt="Premium Gift Set" />
		<h4 class="gwp-title">Premium Gift Set</h4>
	</div>
	<p data-content-gwp-message class="message-premium"></p>
</gift-with-purchase>

Custom Styling Examples

/* Minimal style */
.gwp-minimal {
	--gwp-padding: 0.75rem;
	--gwp-border-radius: 4px;
	--gwp-bg-active: #f0f8f0;
}

/* Bold style */
.gwp-bold {
	--gwp-padding: 1.5rem;
	--gwp-border-radius: 12px;
	--gwp-bg-active: #28a745;
	--gwp-text-active: white;
}

/* Compact mobile style */
@media (max-width: 768px) {
	gift-with-purchase {
		--gwp-image-size: 40px;
		--gwp-padding: 0.5rem;
		--gwp-gap: 0.5rem;
	}
}

Browser Support

  • Chrome/Edge 88+
  • Firefox 85+
  • Safari 14+
  • All modern browsers with Custom Elements support

TypeScript Support

Type definitions are included in the package:

interface GiftWithPurchaseState {
	currentAmount: number;
	threshold: number;
	convertedThreshold: number;
	variantId: string | null;
	isActive: boolean;
	isAdded: boolean;
	promoEnded: boolean;
	productAvailable: boolean;
	isDisabled: boolean;
	remainingAmount: number;
	currencyRate: number;
}

// Component automatically handles message injection
// Style your content and message elements with any CSS classes

Contributing

Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.

License

MIT License - see LICENSE file for details.