Package Selector with Add-to-Cart Button
The most common product page pattern: the visitor picks an option (1 bottle, 3 bottles, 6 bottles) from a set of cards, then clicks a single "Add to Cart" button. The Package Selector tracks the choice; the button writes to the cart.
What You're Building
- A row of selectable package cards (e.g. buy 1 / buy 3 / buy 6)
- Each card shows the price and optional savings pulled from the backend
- One card is pre-selected by default
- A single "Add to Cart" button that reads the current selection and adds it
Step 1: The Selector in Select Mode
Use data-next-selection-mode="select" so clicking a card only updates the selection — it does not write to the cart yet. Give the selector a data-next-selector-id so the button can reference it.
<div data-next-package-selector
data-next-selector-id="qty-picker"
data-next-selection-mode="select">
<div data-next-selector-card
data-next-package-id="10"
data-next-selected="true">
<h3>1 Bottle</h3>
<span data-next-package-price></span>
</div>
<div data-next-selector-card data-next-package-id="11">
<h3>3 Bottles</h3>
<span data-next-package-price></span>
<del data-next-package-price="compare"></del>
<span>Save <span data-next-package-price="savings"></span></span>
</div>
<div data-next-selector-card data-next-package-id="12">
<h3>6 Bottles</h3>
<span data-next-package-price></span>
<del data-next-package-price="compare"></del>
</div>
</div>
<button data-next-action="add-to-cart"
data-next-selector-id="qty-picker">
Add to Cart
</button>The button reads the current selection from the selector with the matching data-next-selector-id.
:::caution
Never use data-next-selection-mode="swap" (or omit it, since swap is the default) on a selector that also feeds an AddToCartEnhancer button. Clicking a card would write to the cart and the button would write again — resulting in a double add.
:::
Step 2: Add Styling for the Selected State
[data-next-selector-card] {
border: 2px solid #e5e7eb;
border-radius: 8px;
padding: 1rem;
cursor: pointer;
}
[data-next-selector-card].next-selected {
border-color: #2563eb;
background: #eff6ff;
}
[data-next-selector-card].next-loading [data-next-package-price] {
opacity: 0.4;
}Step 3: Auto-Render with Badges
When the options need badges ("Most Popular", "Best Value") or other custom fields, use auto-render mode with a card template.
<div data-next-package-selector
data-next-selector-id="qty-picker"
data-next-selection-mode="select"
data-next-packages='[
{"packageId": 10, "label": "1 Bottle", "selected": true},
{"packageId": 11, "label": "3 Bottles", "badge": "Most Popular"},
{"packageId": 12, "label": "6 Bottles", "badge": "Best Value"}
]'
data-next-package-template-id="pkg-card-tpl">
</div>
<template id="pkg-card-tpl">
<div data-next-selector-card>
<span class="badge">{package.badge}</span>
<h3>{package.label}</h3>
<span data-next-package-price></span>
<del data-next-package-price="compare"></del>
<span class="savings">Save <span data-next-package-price="savings"></span></span>
</div>
</template>
<button data-next-action="add-to-cart"
data-next-selector-id="qty-picker">
Add to Cart
</button>Any field in the JSON definition is available as {package.<fieldName>} in the template.
Step 4: Inline Quantity Controls
When a card should allow the visitor to set a custom quantity before adding, add +/− buttons inside the card:
<div data-next-selector-card
data-next-package-id="10"
data-next-quantity="1"
data-next-min-quantity="1"
data-next-max-quantity="10"
data-next-selected="true">
<h3>Product</h3>
<button data-next-quantity-decrease>−</button>
<span data-next-quantity-display>1</span>
<button data-next-quantity-increase>+</button>
<span data-next-package-price></span>
</div>The +/− buttons are automatically disabled at the min/max limits. In select mode the quantity is passed to the add-to-cart button when it's clicked.
Step 5: Show the Current Selection Elsewhere
Display the selected package name or price anywhere on the page using data-next-display:
<!-- Shows the selected package name, updating live as the visitor switches -->
<p>Selected: <strong data-next-display="selection.qty-picker.name">—</strong></p>
<p>Price: <span data-next-display="selection.qty-picker.price">—</span></p>The value after selection. must match the data-next-selector-id.
Full Example
<!-- Package selector -->
<div class="package-selector"
data-next-package-selector
data-next-selector-id="product"
data-next-selection-mode="select"
data-next-packages='[
{"packageId": 10, "label": "1 Bottle", "desc": "Try it out", "selected": true},
{"packageId": 11, "label": "3 Bottles", "desc": "Most popular", "badge": "Popular"},
{"packageId": 12, "label": "6 Bottles", "desc": "Best value", "badge": "Best Value"}
]'
data-next-package-template-id="pkg-tpl">
</div>
<template id="pkg-tpl">
<div data-next-selector-card class="pkg-card">
<div class="pkg-badge">{package.badge}</div>
<h3>{package.label}</h3>
<p class="pkg-desc">{package.desc}</p>
<div class="pkg-price">
<span data-next-package-price></span>
<del data-next-package-price="compare"></del>
</div>
<p class="pkg-savings">Save <span data-next-package-price="savings"></span></p>
</div>
</template>
<!-- Add to cart -->
<button class="btn-add-to-cart"
data-next-action="add-to-cart"
data-next-selector-id="product">
Add to Cart
</button>
<style>
.package-selector {
display: flex;
gap: 0.75rem;
margin-bottom: 1.5rem;
}
.pkg-card {
flex: 1;
position: relative;
border: 2px solid #e5e7eb;
border-radius: 10px;
padding: 1rem;
cursor: pointer;
text-align: center;
}
.pkg-card.next-selected {
border-color: #2563eb;
background: #eff6ff;
}
.pkg-badge:empty { display: none }
.pkg-badge {
position: absolute;
top: -0.6rem;
left: 50%;
transform: translateX(-50%);
background: #2563eb;
color: white;
font-size: 0.7rem;
padding: 0.15rem 0.5rem;
border-radius: 999px;
white-space: nowrap;
}
.pkg-savings:empty { display: none }
.btn-add-to-cart {
width: 100%;
padding: 1rem;
background: #2563eb;
color: white;
border: none;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
}
.btn-add-to-cart[data-next-loading="true"] {
opacity: 0.7;
pointer-events: none;
}
</style>Reacting to Selection Changes
window.nextReady.push(() => {
window.next.on('selector:selection-changed', ({ selectorId, packageId, quantity }) => {
if (selectorId === 'product') {
console.log('Visitor picked package', packageId, 'qty', quantity);
}
});
});