Bundle Set Sale
Use the Bundle Selector to offer visitors pre-built product sets — Buy 1, Buy 2, Buy 3, or any fixed combination — where clicking a card instantly swaps the cart to that bundle. Prices are fetched from the backend so offer conditions and coupon discounts are always reflected accurately.
What You're Building
A "Buy 1 / Buy 2 / Buy 3" selector where:
- Each option is a card the visitor clicks to select
- Selecting a card immediately replaces the previous bundle in the cart (atomic swap — no double-add)
- Each card shows a backend-calculated total price with optional savings
- Per-bundle discount codes are applied/removed automatically when the visitor switches
Step 1: Basic Bundle Cards
The simplest setup is writing cards directly in HTML. Give each card a unique data-next-bundle-id and list the packages it contains.
<div data-next-bundle-selector>
<div data-next-bundle-card
data-next-bundle-id="buy1"
data-next-bundle-items='[{"packageId": 10, "quantity": 1}]'
data-next-selected="true">
<h3>Buy 1</h3>
<p><span data-next-bundle-price></span></p>
</div>
<div data-next-bundle-card
data-next-bundle-id="buy2"
data-next-bundle-items='[{"packageId": 10, "quantity": 2}]'>
<h3>Buy 2</h3>
<p><span data-next-bundle-price></span></p>
<p>Save <span data-next-bundle-price="savings"></span></p>
</div>
<div data-next-bundle-card
data-next-bundle-id="buy3"
data-next-bundle-items='[{"packageId": 10, "quantity": 3}]'>
<h3>Buy 3</h3>
<p><span data-next-bundle-price></span></p>
<p>Save <span data-next-bundle-price="savings"></span></p>
<del><span data-next-bundle-price="compare"></span></del>
</div>
</div>data-next-selected="true" on the first card pre-selects it and adds it to the cart on page load.
Step 2: Add Styling for the Selected State
The enhancer adds CSS classes you can target:
/* All cards */
.next-bundle-card {
border: 2px solid #ddd;
border-radius: 8px;
padding: 1rem;
cursor: pointer;
}
/* Selected card */
.next-bundle-card.next-selected {
border-color: #2563eb;
background: #eff6ff;
}
/* Price is loading */
.next-bundle-card.next-loading [data-next-bundle-price] {
opacity: 0.4;
}Step 3: Per-Bundle Discount Codes
If you have coupon codes specific to each bundle size, declare them on the card. When the visitor switches bundles the old code is removed and the new one is applied — automatically.
<div data-next-bundle-card
data-next-bundle-id="buy2"
data-next-bundle-items='[{"packageId": 10, "quantity": 2}]'
data-next-bundle-vouchers="SAVE10">
Buy 2 — 10% Off
</div>
<div data-next-bundle-card
data-next-bundle-id="buy3"
data-next-bundle-items='[{"packageId": 10, "quantity": 3}]'
data-next-bundle-vouchers="SAVE20">
Buy 3 — 20% Off
</div>:::caution
Do not also manage these codes via CouponEnhancer. The bundle selector owns the voucher lifecycle for codes declared with data-next-bundle-vouchers.
:::
Step 4: Auto-Render Mode
When you have more than 2–3 options or want to keep HTML lean, define bundles as JSON and write a single card template.
<div data-next-bundle-selector
data-next-bundle-template-id="bundle-card-tpl"
data-next-bundles='[
{"id":"buy1","label":"Buy 1","sublabel":"Regular price","items":[{"packageId":10,"quantity":1}]},
{"id":"buy2","label":"Buy 2","sublabel":"Save 10%","items":[{"packageId":10,"quantity":2}],"vouchers":["SAVE10"]},
{"id":"buy3","label":"Buy 3","sublabel":"Best value","badge":"Most Popular","items":[{"packageId":10,"quantity":3}],"vouchers":["SAVE20"]}
]'>
</div>
<template id="bundle-card-tpl">
<div data-next-bundle-card data-next-bundle-id="{bundle.id}">
<span class="badge">{bundle.badge}</span>
<h3>{bundle.label}</h3>
<p class="sublabel">{bundle.sublabel}</p>
<p class="price"><span data-next-bundle-price></span></p>
<p class="savings">Save <span data-next-bundle-price="savings"></span></p>
<del><span data-next-bundle-price="compare"></span></del>
</div>
</template>Any field in the JSON definition is available as {bundle.<fieldName>} in the template.
Step 5: Multi-Product Bundles
A bundle can include more than one product. List each package in the items array.
<div data-next-bundle-selector>
<!-- Single product -->
<div data-next-bundle-card
data-next-bundle-id="solo"
data-next-bundle-items='[{"packageId": 10, "quantity": 1}]'
data-next-selected="true">
Just the Main Product
<span data-next-bundle-price></span>
</div>
<!-- Main product + free gift (hidden from slot list) -->
<div data-next-bundle-card
data-next-bundle-id="with-gift"
data-next-bundle-items='[
{"packageId": 10, "quantity": 1},
{"packageId": 99, "quantity": 1, "noSlot": true}
]'>
Main Product + Free Gift
<span data-next-bundle-price></span>
</div>
</div>"noSlot": true adds the package to the cart silently — no slot row is rendered for it.
Step 6: Variant Selection Per Slot
When your products have variants (color, size), add a slot template so visitors can configure each item in the bundle independently.
<div data-next-bundle-selector
data-next-bundle-slot-template-id="slot-tpl">
<div data-next-bundle-card
data-next-bundle-id="duo"
data-next-bundle-items='[{"packageId": 10, "quantity": 2, "configurable": true}]'
data-next-selected="true">
<h3>Buy 2 — Pick Your Colors</h3>
<div data-next-bundle-slots></div>
<p>Total: <span data-next-bundle-price></span></p>
</div>
</div>
<template id="slot-tpl">
<div class="slot-row">
<img src="{item.image}" alt="{item.name}" />
<strong>Unit {slot.unitNumber}</strong>
<div data-next-variant-selectors></div>
</div>
</template>"configurable": true expands a quantity-2 item into 2 individual slots, each with its own variant dropdowns. Without it, the item renders as a single slot with one set of dropdowns that applies to all units.
Full Example
<!-- Bundle selector -->
<div class="bundle-selector"
data-next-bundle-selector
data-next-bundle-template-id="bundle-tpl"
data-next-bundles='[
{
"id": "buy1",
"label": "Buy 1",
"badge": "",
"items": [{"packageId": 10, "quantity": 1}]
},
{
"id": "buy2",
"label": "Buy 2",
"badge": "Popular",
"items": [{"packageId": 10, "quantity": 2}],
"vouchers": ["SAVE10"]
},
{
"id": "buy3",
"label": "Buy 3",
"badge": "Best Value",
"items": [{"packageId": 10, "quantity": 3}],
"vouchers": ["SAVE20"]
}
]'>
</div>
<template id="bundle-tpl">
<div data-next-bundle-card data-next-bundle-id="{bundle.id}">
<span class="badge">{bundle.badge}</span>
<h3>{bundle.label}</h3>
<div class="pricing">
<span data-next-bundle-price></span>
<del data-next-bundle-price="compare"></del>
</div>
<p class="savings">You save <span data-next-bundle-price="savings"></span></p>
</div>
</template>
<style>
.bundle-selector {
display: flex;
gap: 1rem;
}
[data-next-bundle-card] {
flex: 1;
border: 2px solid #e5e7eb;
border-radius: 8px;
padding: 1.25rem;
cursor: pointer;
position: relative;
}
[data-next-bundle-card].next-selected {
border-color: #2563eb;
background: #eff6ff;
}
.badge:empty {
display: none;
}
/* Hide savings row when there are none */
[data-next-bundle-card].next-loading .pricing {
opacity: 0.5;
}
</style>Reacting to Bundle Changes
window.nextReady.push(() => {
window.next.on('bundle:selection-changed', ({ bundleId, items }) => {
console.log('Switched to bundle:', bundleId);
// items = [{ packageId, quantity }, ...]
});
});