# BundleConfigurator

> Build-your-own bundle selector with product limits, repeat quantities, live pricing, and grouped cart lines.

Canonical URL: https://c22dfdd27662:3005/islands/commerce/bundle-configurator

> Use `BundleConfigurator` for mix-and-match boxes where shoppers choose a
> required number of products or variants. It supports repeated products,
> tiered pricing, a live bundle summary, and one grouped add-to-cart action.

## Preview

**Category:** Commerce

Use [BundleBuilder](/islands/commerce/bundle-builder) instead when the offer is
a fixed "main product plus recommendations" bundle without slot rules.

## Props

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `productIds` | `string[]` | One product source required | - | Shopify Product GIDs resolved before hydration |
| `products` | `PresentedStorefrontProduct[]` | One product source required | `[]` | Resolved product objects, primarily for previews or advanced authored data |
| `bundle` | `object` | Yes | - | Bundle identity and transform parent: `{ handle, parentVariantId, title? }` |
| `minItems` | `number` | Yes | - | Minimum selected item count |
| `maxItems` | `number` | Yes | - | Maximum number of visible slots and selected items |
| `productMaxQuantity` | `number` | No | `maxItems` | Maximum times one selected variant can appear |
| `discountTiers` | `array` | Yes | - | Pricing tiers with `minItems`, `type`, `value`, and optional `label` |
| `layout` | `"grid" \| "split" \| "steps"` | No | `"grid"` | Layout identifier exposed through `data-layout` for section styling |
| `allowRepeats` | `boolean` | No | `true` | Allow the same variant to fill multiple slots |
| `showProgress` | `boolean` | No | `true` | Show the bundle completion progress bar |
| `showSummary` | `boolean` | No | `true` | Show the selected slots, total, clear action, and cart CTA |
| `ctaText` | `string` | No | `"Add Bundle to Cart"` | Final cart button label |
| `requireTransformConfirmation` | `boolean` | No | `true` | Require the configured parent variant to appear after the cart transform runs |

At least one of `productIds` or `products` must be a non-empty array.
`bundle.parentVariantId` must be a Shopify ProductVariant GID.

## Pricing Tiers

Each tier activates when the selected count reaches `minItems`. If multiple
tiers match, the tier with the highest `minItems` wins.

| Type | Meaning |
|------|---------|
| `fixed_total` | Sets the complete bundle total to `value` |
| `percentage` | Reduces the selected-item subtotal by `value` percent |
| `fixed_discount` | Subtracts `value` from the selected-item subtotal |

Money values use the product currency's major unit. For example,
`{ "type": "fixed_total", "value": 659 }` displays ₹659 for INR products.
Products with mixed currencies cannot be added as one bundle.

## Embed Example

```html
<lx-island name="BundleConfigurator" hydrate="immediate">
  <script type="application/json">
    {
      "productIds": [
        "gid://shopify/Product/101",
        "gid://shopify/Product/102",
        "gid://shopify/Product/103",
        "gid://shopify/Product/104"
      ],
      "bundle": {
        "handle": "hydration-three-pack",
        "parentVariantId": "gid://shopify/ProductVariant/1999",
        "title": "Your Bundle"
      },
      "minItems": 3,
      "maxItems": 3,
      "productMaxQuantity": 3,
      "discountTiers": [
        {
          "minItems": 3,
          "type": "fixed_total",
          "value": 659,
          "label": "Add 3 products to get the bundle at ₹659"
        }
      ],
      "layout": "grid",
      "allowRepeats": true,
      "showProgress": true,
      "showSummary": true,
      "ctaText": "Add To Cart"
    }
  </script>
</lx-island>
```

## Shopify Bundle Requirement

The island calculates and displays the offer, but Shopify must enforce the
same bundle definition at cart and checkout.

For production:

1. Configure a Shopify Cart Transform for the bundle handle.
2. Set `bundle.parentVariantId` to the transform's parent bundle variant.
3. Keep `requireTransformConfirmation: true`.
4. Make the transform validate allowed variants, quantity limits, and the
   selected pricing tier.

Only set `requireTransformConfirmation: false` in isolated previews or tests.
Without transform confirmation, the storefront can display a bundle total
that Shopify has not enforced.

## Cart Implementation

When the shopper submits a complete bundle, the island:

1. Aggregates repeated selections into one cart line per variant.
2. Adds all selected lines through the shared cart action.
3. Applies the same private line attributes to every bundle line:
   `_lx_bundle_handle`, `_lx_bundle_instance`, `_lx_bundle_parent`, and
   `_lx_bundle_currency`.
4. Confirms that the parent bundle variant exists in refreshed cart state when
   transform confirmation is enabled.
5. Opens the configured cart profile after a successful add.

Unavailable variants cannot fill a slot. Multi-variant products display a
native option selector, and changing the selector affects subsequent adds.

## Events

| Event | Direction | Payload |
|-------|-----------|---------|
| `bundle:add` | Emits | `{ products, bundleHandle, bundleInstanceId, total, currencyCode }` after a successful grouped add |
| `cart:open` | Emits | `{ ts }` at acceptance in the optimistic runtime; after bundle confirmation in legacy mode |

`products` contains aggregated variant IDs and quantities.

## Styling

The island follows the storefront theme variables:

- `--lx-accent-color`
- `--lx-bg-color`
- `--lx-bg-surface`
- `--lx-border-color`
- `--lx-text-color`
- `--lx-text-muted`

Target internal elements through `data-part` selectors:

`root`, `progress`, `products`, `product-card`, `product-image`,
`product-title`, `variant-select`, `add`, `quantity-badge`, `summary`,
`summary-title`, `instruction`, `count`, `slots`, `clear`, `total`,
`subtotal`, `cta`, `error`, and `empty`.

```css
[data-section-id='bundle-offer'] [data-part='product-card'] {
  border-radius: 0.75rem;
}

[data-section-id='bundle-offer'] [data-part='cta'],
[data-section-id='bundle-offer'] [data-part='add'] {
  border-radius: 0;
  text-transform: uppercase;
}
```

## Related Islands

- [BundleBuilder](/islands/commerce/bundle-builder) — Fixed frequently bought together offers
- [DrawerShell](/islands/cart/drawer-shell) — Cart profile container
- [CartLines](/islands/cart/cart-lines) — Displays transformed bundle lines
- [CartSummary](/islands/cart/cart-summary) — Displays the enforced cart total
- [CartCheckoutButton](/islands/cart/cart-checkout-button) — Checkout action
