Selectors
There are four ways to let the customer pick which package gets submitted as the upsell. Three live inside an offer container; one links to an external selector enhancer.
| Pattern | Where the options live | Use when |
|---|---|---|
| Built-in option cards | Inside the offer container | The offer card and the chooser are one widget |
| Built-in dropdown | Inside the offer container | You want a compact <select> instead of cards |
| Linked PackageSelector | Outside the offer container | The selector and the accept button are separate elements on the page |
| Linked BundleSelector | Outside the offer container | The upsell is a multi-package bundle |
Built-in Option Cards
Use data-next-upsell-selector on a container, then data-next-upsell-option on each card. Each card needs its own data-next-package-id.
<div data-next-upsell-selector data-next-selector-id="protection">
<h3>Choose Your Protection Plan</h3>
<div data-next-upsell-option data-next-package-id="50">
<h4>Basic</h4>
<p>1 year coverage — $14.99</p>
</div>
<div data-next-upsell-option data-next-package-id="51" data-next-selected="true">
<h4>Premium</h4>
<p>2 year coverage + accidental damage — $24.99</p>
</div>
<div data-next-upsell-option data-next-package-id="52">
<h4>Ultimate</h4>
<p>3 year coverage + everything — $39.99</p>
</div>
<button data-next-upsell-action="add">Add selected protection</button>
<button data-next-upsell-action="skip">No protection, thanks</button>
</div>The selected option gets the next-selected class and data-next-selected="true". Pre-select a card by setting data-next-selected="true" on it in the markup.
The enhancer emits upsell:option-selected with { selectorId, packageId } whenever the selection changes.
Built-in Dropdown
Use a native <select> with data-next-upsell-select="<selectorId>". Each <option> value is a package ID.
<div data-next-upsell-selector data-next-selector-id="training">
<h3>Add a training course?</h3>
<select data-next-upsell-select="training">
<option value="">Choose a course...</option>
<option value="2">Beginner — $29.99</option>
<option value="3" selected>Advanced — $49.99</option>
<option value="4">Master — $79.99</option>
</select>
<button data-next-upsell-action="add">Add course to order</button>
<button data-next-upsell-action="skip">No thanks</button>
</div>The default <option> (with selected) is picked up on init. Selecting an empty value clears the selection and disables submission.
You can also place a bare <select> element with data-next-upsell-select and no wrapping container — it activates on its own.
Linked PackageSelector
Run a PackageSelectorEnhancer outside the offer container, then point an Accept Button at it via a shared data-next-selector-id.
<div
data-next-package-selector
data-next-selector-id="upsell-pkg"
data-next-upsell-context>
<div data-next-selector-card data-next-package-id="42" data-next-selected="true">
1 bottle — $29
</div>
<div data-next-selector-card data-next-package-id="43">
3 bottles — $69
</div>
</div>
<button
data-next-action="accept-upsell"
data-next-selector-id="upsell-pkg">
Add to my order
</button>data-next-upsell-context on the selector is required. It puts the selector into select mode and disables all cart writes. Without it, clicking a card would write to the cart, which is wrong on a post-purchase page.
You can also link a linked package selector to an offer container by setting data-next-package-selector-id="<selector-id>" on the container, but the accept-button pattern above is simpler.
Linked BundleSelector
Run a BundleSelectorEnhancer outside the offer, then link it to an Accept Button using data-next-upsell-action-for (note: this is a different attribute from data-next-selector-id).
<div
data-next-bundle-selector
data-next-selector-id="upsell-bundle"
data-next-upsell-context>
<div data-next-bundle-card
data-next-bundle-id="solo"
data-next-bundle-items='[{"packageId":42,"quantity":1}]'
data-next-selected="true">
Single
</div>
<div data-next-bundle-card
data-next-bundle-id="duo"
data-next-bundle-items='[{"packageId":42,"quantity":1},{"packageId":43,"quantity":1}]'>
Duo Pack
</div>
</div>
<button
data-next-action="accept-upsell"
data-next-upsell-action-for="upsell-bundle">
Add bundle to my order
</button>When the linked bundle is selected, the accept button submits all items in the bundle as a single addUpsell call. One upsell:accepted event is emitted per package.
You can also link a bundle selector to an offer container by setting data-next-bundle-selector-id="<selector-id>" on the container, or by nesting the bundle selector as a child of the container — the offer container auto-detects child bundle selectors.
Cross-Container Sync
When two containers share the same data-next-selector-id value, selecting an option in one syncs the selection in the other. This is useful for sticky headers, side-by-side comparison views, or duplicating the offer above and below the fold.
<!-- Sticky header version -->
<div class="sticky-header" data-next-upsell-selector data-next-selector-id="protection">
<!-- compact options -->
</div>
<!-- Full version below the fold -->
<div data-next-upsell="offer" data-next-selector-id="protection">
<!-- full option cards -->
</div>Next Steps
- Add quantity controls to a selected option: Quantity
- Wire it into a multi-page funnel: Page Flow
- All selector attributes: Reference → Attributes