> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kleep.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Anti-Bracketing

> Detect size bracketing in the cart and prompt shoppers to keep one size, via the Kleep JS library.

> [!NOTE]
>
> **Prerequisites** — before implementing Anti-Bracketing, complete the [Cookie consent](/cms/js-library#1-cookie-consent-legal-requirement) and [Installation](/cms/js-library#2-installation) steps on the [JS Library](/cms/js-library) page. Kleep must not load until the visitor has consented via your CMP, and the library must be loaded site-wide.

> [!NOTE]
>
> Anti-Bracketing is available on demand. If you're unsure whether this feature is enabled for your account, contact your Kleep representative.

## What Anti-Bracketing does

When a shopper adds **two or more sizes of the same product** to their cart, Anti-Bracketing opens a prompt asking them to commit to one size. It can optionally display Kleep's AI size recommendation to help them choose confidently.

The prompt appears as a **side panel** (sliding in from the right) or a **modal** (centered overlay), depending on your configuration. Shoppers can always dismiss it and keep all sizes.

## How to implement

**Step 1: Wait for the SDK**

The `kleep.antiBracketing` method is available once the widget script has loaded. Wait for it before initializing:

```javascript theme={null}
const waitForKleep = () => new Promise(resolve => {
  const check = () =>
    window.kleep && window.kleep.antiBracketing
      ? resolve()
      : setTimeout(check, 50)
  check()
})

await waitForKleep()
```

**Step 2: Initialize with your cart adapter**

The adapter tells the widget how to read your cart and (optionally) remove lines.

```javascript theme={null}
const { adapters } = window.kleep.antiBracketing

const ab = window.kleep.antiBracketing({
  public_id: "your-retailer-uuid",   // provided by Kleep
  lang: "en",                         // ISO 639-1 language code

  adapter: adapters.custom({
    // Required: return the current cart contents
    getCart: async () => ({
      cartId: "my-cart-id",
      lines: [
        { productId: "product-123", variantId: "variant-S", quantity: 1, size: "S" },
        { productId: "product-123", variantId: "variant-M", quantity: 1, size: "M" },
      ]
    }),

    // Optional: let the SDK remove lines when the shopper keeps one size
    setLineQuantity: async (line, quantity) => {
      await yourCartAPI.updateLine(line.variantId, quantity)
    }
  })
})
```

> [!WARNING]
>
> The `getCart` callback must return **all** cart lines with `productId`, `variantId`, `quantity`, and `size`. The widget groups lines by `productId` to detect bracketing — if two lines share the same `productId` with different sizes, the prompt will trigger.

**Step 3: Start listening**

```javascript theme={null}
ab.start()
```

This starts the reconcile loop. The widget will call `getCart`, detect bracketing, and show the prompt when appropriate.

**Step 4: Notify on cart changes**

Call `notifyCartChanged()` whenever the cart is updated (add to cart, remove, quantity change) so the widget re-checks:

```javascript theme={null}
ab.notifyCartChanged()
```

> [!TIP]
>
> Call this after every cart mutation — add to cart, remove from cart, quantity change. The widget will re-read the cart via your `getCart` adapter and react accordingly.

**Step 5: Handle the shopper's choice**

```javascript theme={null}
ab.on('resolve', ({ productId, keepSize, applied }) => {
  // keepSize = the size the shopper chose to keep, or null if they chose "keep both"
  // applied = true if the SDK already removed the other lines via setLineQuantity

  if (keepSize && !applied) {
    // You need to remove the non-kept lines from the cart yourself
  }

  // Refresh your cart UI
  refreshCartUI()
})
```

If you provided `setLineQuantity` in the adapter, the SDK removes the unkept lines automatically (`applied` will be `true`). Otherwise, handle removal in the `resolve` callback.

**Step 6: Cleanup**

```javascript theme={null}
// When leaving the page or unmounting the component
ab.destroy()
```

## Full example

```html theme={null}
<script src="https://module.kleep.ai/api/widget"></script>
<script>
  (async () => {
    // Wait for SDK
    await new Promise(resolve => {
      const check = () =>
        window.kleep && window.kleep.antiBracketing
          ? resolve()
          : setTimeout(check, 50)
      check()
    })

    const { adapters } = window.kleep.antiBracketing

    const ab = window.kleep.antiBracketing({
      public_id: "your-retailer-uuid",
      lang: "en",
      adapter: adapters.custom({
        getCart: async () => ({
          cartId: "cart-1",
          lines: getYourCartLines()   // return your cart data here
        }),
        setLineQuantity: async (line, qty) => {
          await yourCartAPI.updateLine(line.variantId, qty)
        }
      })
    })

    ab.on('resolve', ({ keepSize, applied }) => {
      if (keepSize && !applied) {
        // fallback: remove the non-kept lines yourself
      }
      refreshCartUI()
    })

    ab.start()

    // Notify on every cart change
    document.querySelector('.add-to-cart')
      .addEventListener('click', () => {
        // after your add-to-cart logic completes:
        ab.notifyCartChanged()
      })
  })()
</script>
```

## Configuration

All visual settings — trigger frequency, layout, wordings, content toggles — are managed from the **Kleep dashboard** at **AI Sizing → Anti-bracketing**. The widget reads them from the Kleep CDN at runtime, so you do not need to pass them in code.

You can override specific settings at initialization time if needed:

```javascript theme={null}
const ab = window.kleep.antiBracketing({
  public_id: "your-retailer-uuid",
  lang: "en",
  display: "modal",                        // override layout: "panel" | "modal"
  trigger: {
    frequency: "once_per_session",         // "once_per_product" | "once_per_session" | "always"
    scope: "product_page",                 // "product_page" | "any"
  },
  getCurrentProductId: () => "product-123", // required when scope = "product_page"
  config: {
    content: {
      showKeepChoices: true,
      showRecommendation: true,
    },
    style: {
      fontFamily: '"Your Font", sans-serif',
      buttonBackground: '#000000',
      buttonTextColor: '#ffffff',
    }
  },
  adapter: adapters.custom({ /* ... */ })
})
```

See the [Reference](#reference) section below for the full list of parameters.

## Reference

### Initialization parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `public_id` | `string` | Yes | Your retailer UUID (provided by Kleep) |
| `lang` | `string` | Yes | Language code (ISO 639-1): `en`, `fr`, `de`, `it`, `es`, `nl`, `pt`, `da`, `sv`, `pl`, `ja`, `ko`, `zh` |
| `adapter` | `Adapter` | Yes | Cart adapter (see below) |
| `debug` | `boolean` | No | Enable debug logging in the browser console |
| `display` | `"panel"` \| `"modal"` | No | Override the layout set in the dashboard |
| `trigger.frequency` | `string` | No | `"once_per_product"` \| `"once_per_session"` \| `"always"` |
| `trigger.scope` | `string` | No | `"product_page"`: only on the bracketed product's PDP. `"any"`: anywhere |
| `getCurrentProductId` | `() => string` | When scope = `"product_page"` | Returns the current PDP's product ID |
| `config.content.showKeepChoices` | `boolean` | No | Override the keep-choices toggle |
| `config.content.showRecommendation` | `boolean` | No | Override the recommendation toggle |
| `config.style` | `object` | No | Visual overrides (see Style options) |

### Adapter contract

The adapter tells the widget how to read and modify your cart.

```javascript theme={null}
adapters.custom({
  // Required: return the current cart
  getCart: async () => ({
    cartId: "optional-cart-id",
    lines: [
      {
        lineId: "optional-line-id",     // your internal line ID
        productId: "product-123",       // groups items — same productId = same product
        variantId: "variant-S",         // identifies the specific size variant
        quantity: 1,
        size: "S"                       // the size label shown to the shopper
      }
    ]
  }),

  // Optional: remove or update a cart line
  // Called with quantity = 0 to remove a line
  setLineQuantity: async (line, quantity) => { /* ... */ },

  // Optional: subscribe to external cart-change events
  // Return an unsubscribe function
  subscribe: (callback) => {
    window.addEventListener('cart:updated', callback)
    return () => window.removeEventListener('cart:updated', callback)
  }
})
```

### Instance methods

| Method | Description |
| - | - |
| `ab.start()` | Start watching the cart for bracketing |
| `ab.notifyCartChanged()` | Signal that the cart was updated — triggers a re-check |
| `ab.destroy()` | Remove all listeners and clean up |
| `ab.getConfig()` | Returns a Promise with the resolved configuration |

### Events

| Event | Payload | Description |
| - | - | - |
| `bracketing` | `{ productId, sizes, lines }` | Bracketing detected for a product |
| `resolve` | `{ productId, keepSize, applied }` | Shopper made a choice. `keepSize` = chosen size or `null` (keep both). `applied` = `true` if the SDK removed lines via `setLineQuantity` |
| `cartChange` | `{ source, cart, changes }` | Cart contents changed |
| `error` | `Error` | An error occurred |

### Style options

Visual overrides passed via `config.style`:

| Property | Default | Description |
| - | - | - |
| `fontFamily` | `inherit` | Font family |
| `textColor` | `#272626` | Text color |
| `background` | `#ffffff` | Background color |
| `overlayColor` | `rgba(0,0,0,0.35)` | Modal/panel overlay color |
| `borderRadius` | `20px` | Container border radius |
| `buttonBackground` | `#2e2e2e` | Confirm button background |
| `buttonTextColor` | `#ffffff` | Confirm button text color |
| `buttonRadius` | `8px` | Button border radius |
| `radioAccentColor` | `#2e2e2e` | Radio button accent color |

***

## Cookies & data privacy

Kleep is fully gated by visitor consent: the script only loads **after** consent is collected through your CMP, and you must condition its loading accordingly. For the complete list of trackers, the data processed, the purposes and legal bases, hosting, subprocessors and security, see [Cookies, CMP & Data Privacy](/cookie-consent).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.