# BuyBox

> Primary add-to-cart component. Place once per PDP. Handles variant selection, quantity, and Shopify checkout.

Canonical URL: https://fa7e86e3d553:3005/islands/commerce/buy-box

Primary add-to-cart component. Place once per PDP. Handles variant selection, quantity, and Shopify checkout.

## Preview

**Category**: Commerce

## Props

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| product | object | Yes | - | Product object with title, price, compareAtPrice (optional), variants array |
| ctaText | string | No | "Add to Cart" | Call-to-action button text |
| loadingText | string | No | "Adding..." | Text while the cart mutation is pending |
| addedText | string | No | "Added!" | Success confirmation text |
| loadingIndicator | `"spinner" \| "none"` | No | `"spinner"` | Loading indicator |
| successIndicator | `"check" \| "none"` | No | `"check"` | Success indicator |
| listenForEvents | boolean | No | false | Whether to listen for variant:changed and subscription:changed events |
| syncKey | string | No | - | Match a StickyBar key for two-way variant/quantity synchronization and shared cart state; supported in rendered and headless mode |
| showVariantSelector | boolean | No | true | Whether to show built-in variant selector. Set false when using external VariantSwatches |
| showPrice | boolean | No | true | Show the selected variant price |
| showTrustBadges | boolean | No | true | Show expanded-layout trust badges |
| variant | enum | No | "default" | Visual style: "default", "compact", or "expanded" |
| animate | boolean | No | true | Whether to animate add-to-cart interaction |
| buttonStyle | object | No | - | CTA button style overrides: `{borderRadius?, padding?, fontSize?}`. Inline styles override Tailwind defaults |

## Embed Example

```html
<lx-island name="BuyBox">
  <script type="application/json">
{
  "product": {
    "title": "example-title",
    "price": "example-price",
    "variants": []
  }
}
  </script>
</lx-island>
```

## Usage Patterns

### Synchronized purchase controls

Give multiple BuyBoxes and/or StickyBar the same unique `syncKey` and complete
variant catalog. They share variant, quantity, price, availability, selling
plan, loading, success, and error state.

When `listenForEvents` is enabled, place external selectors in the same
`data-scope`. A `syncKey` identifies a purchase group; use a different key for
each independent product.

**Standalone (minimum viable PDP):**

```html
<lx-island name="BuyBox">
  <script type="application/json">
{
  "product": {
    "title": "example-title",
    "price": "example-price",
    "variants": []
  }
}
  </script>
</lx-island>
```

**Side-by-side with ProductGallery (PDP layout):**

```html
<lx-island name="ProductGallery">
  <script type="application/json">
{}
  </script>
</lx-island>

<lx-island name="BuyBox">
  <script type="application/json">
{
  "product": {
    "title": "example-title",
    "price": "example-price",
    "variants": []
  }
}
  </script>
</lx-island>
```

**With external VariantSwatches (listening mode):**

```html
<lx-island name="VariantSwatches">
  <script type="application/json">
{}
  </script>
</lx-island>

<lx-island name="BuyBox">
  <script type="application/json">
{
  "product": {
    "title": "example-title",
    "price": "example-price",
    "variants": []
  }
}
  </script>
</lx-island>
```

## Events

| Event | Direction | Payload |
|-------|-----------|---------|
| cart:add | Emits | `{variantId: string, quantity: number}` when item added to cart |
| variant:changed | Listens | Full scoped selected-variant state from VariantSwatches, VariantSelector, or OptionResolver |
| subscription:changed | Listens | `{planId: string, frequency: string, price: string}` from SubscriptionToggle |
| inventory:updated | Listens | `{variantId: string, quantity: number, available: boolean}` |

The root exposes `data-selected-variant-id`, `data-selected-available`, and
`data-quantity`.

## Out-of-Stock: Notify Me

When every variant is unavailable (`product.variants.every(v => !v.available)`) and no available variant is selected, BuyBox automatically swaps its CTA for a "Notify Me" email form — no extra props needed.

- Captures the visitor's email (plus `product_title` and `variant_id`) and posts it to the first-party form-capture backend under `form_key: "notify_me"` — visible in the storefront dashboard under **Forms**.
- Shows a success state only after the backend confirms the write; on failure the form stays interactive so the visitor can retry.
- There is no endpoint to configure — this is fully automatic, same mechanism as [EmailCapture](/islands/engagement/email-capture) and [Footer](/islands/navigation/footer)'s newsletter form.

## Tips

- A standard rendered BuyBox is the supported default. Headless markup is
  optional, not required for normal PDPs.
- Multiple BuyBoxes are supported when they intentionally share a `syncKey`.
- Quantity controls always add the displayed quantity and selected Shopify
  variant ID.
- Sold-out variants remain visible, dimmed, struck through, disabled, and
  labelled. If the selected variant becomes unavailable, the CTA is disabled
  instead of silently changing selection.
- Use `variant:"compact"` inside modals or quick-view overlays
- Use `variant:"expanded"` to add trust badges (secure checkout, money-back guarantee) below CTA
- Use `buttonStyle` for brand-specific CTA styling: `{"borderRadius":"9999px","padding":"1rem 2rem","fontSize":"1.125rem"}`
- Cart V2 is enabled by default. Configure an effective profile; the renderer
  supplies the drawer, so do not add cart islands or a Cart V2 flag to new page
  source.
- Successful mutations open the configured drawer. Loading, mutation success,
  visible cart feedback, and mutation errors are separate runtime states.
- Preview reports a warning when purchase controls have no effective cart
  profile.
- If product has 2+ option axes (e.g. Color × Size), use [OptionResolver](/islands/commerce/option-resolver) + [VariantSwatches](/islands/commerce/variant-swatches) in axis mode, set `showVariantSelector:false`
- Set `listenForEvents:true` when using with [VariantSwatches](/islands/commerce/variant-swatches), [SubscriptionToggle](/islands/commerce/subscription-toggle), or [OptionResolver](/islands/commerce/option-resolver)

## Stylable Parts

Target internal elements via `data-part` selectors in section CSS:

| Part | Description |
|------|-------------|
| `cta` | Primary call-to-action button |
| `cta-state` | Current CTA loading/success label |
| `error` | Cart mutation error feedback |
| `notify` | All-variants-unavailable notify form |
| `qty` | Quantity selector wrapper |
| `qty-btn` | Increment/decrement buttons |
| `root` | Outermost container |
| `trust-badges` | Trust badges row |
| `variant-btn` | Individual variant button |
| `variants` | Variant selector container |

```css
/* Example: pill-shaped CTA with gradient */
[data-section-id='SECTION'] [data-part='cta'] {
  border-radius: 9999px;
  background: linear-gradient(90deg, #ec4899, #8b5cf6);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
```

## Related Islands

- [ProductGallery](/islands/commerce/product-gallery) — Coordinate with `listenForVariant:true` to swap gallery images on variant change
- [VariantSwatches](/islands/commerce/variant-swatches) — External variant selector; set BuyBox `showVariantSelector:false` when using
- [OptionResolver](/islands/commerce/option-resolver) — For multi-axis products (Color × Size); emits `variant:changed` → BuyBox
- [SubscriptionToggle](/islands/commerce/subscription-toggle) — Subscribe & Save; emits `subscription:changed` → BuyBox
- [Cart Profiles](/guides/cart-v2) — Renderer-managed Cart V2 feedback
- [StickyBar](/islands/commerce/sticky-bar) — Scroll-triggered bottom CTA alternative
- [EmailCapture](/islands/engagement/email-capture) — Same first-party capture backend as BuyBox's out-of-stock notify-me form
