webdevpuneet.com

Product Bundle Builder — Free Build-Your-Own-Bundle UI (HTML/CSS/JS)

Product Bundle Builder · Cards · Plain HTML, CSS & JS · Live preview

CategoryCards

Copy & paste

Source Code

<div class="pbb-wrap">
  <div class="pbb-products">
    <h3>Build your bundle</h3>
    <p class="pbb-sub">Add 2 items for 5% off, 3+ items for 10% off</p>
    <div class="pbb-grid" id="pbbGrid"></div>
  </div>

  <div class="pbb-summary">
    <h4>Your bundle</h4>
    <div class="pbb-summary-list" id="pbbSummaryList">
      <p class="pbb-empty" id="pbbEmpty">No items added yet</p>
    </div>
    <div class="pbb-totals">
      <div class="pbb-line">
        <span>Subtotal</span>
        <b id="pbbSubtotal">$0.00</b>
      </div>
      <div class="pbb-line pbb-discount-line" id="pbbDiscountLine" hidden>
        <span>Bundle discount (<span id="pbbDiscountPct">0</span>%)</span>
        <b id="pbbDiscountAmt">&minus;$0.00</b>
      </div>
      <div class="pbb-line pbb-total-line">
        <span>Total</span>
        <b id="pbbTotal">$0.00</b>
      </div>
    </div>
    <button type="button" class="pbb-checkout" id="pbbCheckout">Add bundle to cart</button>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Single source of truth
One selected array drives both the product grid and the summary panel.
Tiered auto-discount
5% at 2 items, 10% at 3+, computed by one pure function of the count.
Discount shown as real savings
Subtotal, percentage, dollar discount, and total are all shown separately.
Two removal paths
Deselect from the product card or remove directly from the summary — both stay in sync.
Disabled empty checkout
The checkout button disables itself when no items are selected.
Responsive layout
Sidebar summary collapses to a stacked layout on narrow viewports.
Sticky summary panel
The bundle summary stays visible while scrolling a longer product grid.
Framework-agnostic core
The selection array and discount function port directly to any component model.

About this UI Snippet

Product Bundle Builder — A Discount That Grows as the Cart Does

Screenshot of the Product Bundle Builder snippet rendered live

"Build your own bundle" is a proven e-commerce upsell: let a shopper pick their own combination of add-ons and reward them with a bigger discount the more they add. This snippet builds that interaction in plain HTML, CSS, and vanilla JavaScript — a grid of addable product cards, a running summary sidebar, and a discount rate that automatically steps up as the bundle grows.

One selection array, two views

Selected product ids live in a single selected array. renderProducts() and renderSummary() both read from it — the former to mark which product cards show "Added" instead of "Add to bundle," the latter to build the summary list, subtotal, discount, and total. Adding or removing an item (from either the product grid or the summary's own remove button) mutates that one array and re-renders both views, so the grid and the summary can never fall out of sync.

A discount that scales with commitment

discountRateFor(count) is a tiny, explicit function: 0% for zero or one item, 5% for exactly two, and 10% for three or more. Because it's a pure function of the selected count, the discount badge and dollar amount always match the actual rule, and changing the thresholds or rates later is a one-line edit rather than a hunt through scattered conditionals.

Discount shown as real savings, not just a lower number

The summary shows a full subtotal (the sum of every selected item's price), then a distinct discount line with both the percentage and the dollar amount it represents, and finally the total. For two items — say a $24 case and a $29 charger — the subtotal is $53.00, the 5% discount is $2.65, and the total is $50.35. For three items adding a $12 strap, the subtotal becomes $65.00, the 10% discount is $6.50, and the total is $58.50 — visibly showing the shopper that the *third* item didn't just add its own price, it also improved the discount rate on everything.

Where it fits

Pair it with a product card grid for the base catalog, a product quick view for item detail, or a coupon card / promo-code-input for stacking a manual code on top of the bundle discount.

Customizing it

Swap in your real product catalog and images, adjust the discount tiers and thresholds, add a maximum bundle size, or add per-category exclusivity (e.g. only one item from a "color" group) if your bundles need that constraint.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA 4-product grid renders with an empty bundle summary.
  2. 2
    Add a productClick "+ Add to bundle"; it appears in the summary at full price.
  3. 3
    Add a second productThe summary shows a 5% discount line automatically.
  4. 4
    Add a third productThe discount jumps to 10% — recalculated on every add.
  5. 5
    Remove an itemUse the × in the summary or toggle the product card; the discount tier adjusts down.
  6. 6
    Wire up checkoutReplace the alert() in the checkout button with your real add-to-cart flow.

Real-world uses

Common Use Cases

E-commerce upsells
Let shoppers assemble their own accessory bundle with a growing discount.
Subscription add-on selection
Apply the same pattern to SaaS add-on modules instead of physical products.
Gift set builders
Combine with a product card grid for a curated gift-bundle flow.
Cart upsell modules
Show a mini version of this builder inside a cart drawer to grow order value.
Promotional campaigns
Pair with a coupon card for stacking a seasonal code on top.
Quote-style B2B selection
Adapt for a services or add-on selector where bulk selection earns a discount.

Got questions?

Frequently Asked Questions

discountRateFor(count) is a small pure function: 0 or 1 selected items get no discount, exactly 2 items get 5% off the subtotal, and 3 or more items get 10% off. It only depends on how many items are currently selected, so the discount badge and dollar amount are always in sync with the actual bundle contents.

With a $24 case, $29 charger, and $12 strap selected, the subtotal is 24 + 29 + 12 = $65.00. At the 3-item tier the discount rate is 10%, so the discount is 65.00 × 0.10 = $6.50, and the total is 65.00 − 6.50 = $58.50.

Both views read from the same selected array of product ids — there's no separate "cart" state. Any click that adds or removes an item (from the grid's Add button or the summary's × button) mutates that one array and immediately re-renders both renderProducts() and renderSummary(), so they can never disagree.

renderSummary() recomputes discountRateFor() from the new, smaller selected count on every change, so removing an item immediately recalculates and can show a lower discount tier (or hide the discount line entirely if only one item remains) — it's always freshly derived, never cached from a prior state.

Move the selected array into component state (useState/ref) and derive the subtotal, discount rate, discount amount, and total with useMemo or a computed property whenever it changes. The PRODUCTS catalog and discountRateFor() function port over unchanged as plain data and a pure function.