# PlanSelector

> Subscribe & Save plan cards with benefit checklists and live tier re-pricing. Emits subscription:changed for BuyBox.

Canonical URL: https://fa7e86e3d553:3005/islands/commerce/plan-selector

> Subscribe & Save plan cards — the dominant DTC PDP purchase pattern: large radio cards (Autoship vs One-Time) with per-card price, compare-at, per-unit cost, benefit checklist, and live re-pricing from a QuantityBreaks tier selector. Emits `subscription:changed` so a BuyBox with `listenForEvents:true` picks up the selling plan automatically.

## Preview

**Category**: Commerce

## Props

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| plans | array | Yes | - | Plan cards: `{id, title, price, compareAtPrice?, perUnit?, sellingPlanId?, frequency?, benefits?, note?, badge?, static?}`. A `sellingPlanId` marks a subscription plan; `static:true` pins the card's price against tier changes |
| defaultPlanId | string | No | first plan | Pre-selected plan id |
| variant | enum | No | "cards" | `"cards"` (large radio cards), `"stacked"` (tighter), `"minimal"` (pill toggles) |
| listenForTiers | boolean | No | false | Subscribe to `tiers:changed` from QuantityBreaks — re-prices cards live |
| emitEvents | boolean | No | true | Emit `subscription:changed` on mount and change |
| showBenefits | boolean | No | true | Render the benefit checklist inside cards |

## Embed Example

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

## Usage Patterns

**Re-priced live by a QuantityBreaks tier selector (side by side):**

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

<lx-island name="QuantityBreaks">
  <script type="application/json">
{
  "tiers": [
    {
      "quantity": 0,
      "price": "example-price",
      "perUnit": "example-perUnit"
    }
  ],
  "variantId": "example-variantId"
}
  </script>
</lx-island>
```

**Container slot — tier selector nested inside the first plan card (NuStrips-style):**

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

<lx-island name="QuantityBreaks">
  <script type="application/json">
{
  "tiers": [
    {
      "quantity": 0,
      "price": "example-price",
      "perUnit": "example-perUnit"
    }
  ],
  "variantId": "example-variantId"
}
  </script>
</lx-island>
```

The child HTML renders inside the first plan card (`data-part="plan-slot"`); nested islands hydrate automatically.

## Events

| Event | Direction | Payload |
|-------|-----------|---------|
| subscription:changed | Emits | `{active: boolean, sellingPlanId?: string, price: string, frequency?: string}` on mount and every plan change |
| tiers:changed | Listens (when `listenForTiers:true`) | Subscription cards re-price from `subscribePrice`/`subscribeCompareAt`/`subscribePerUnit` and show the `cadence` line; one-time cards re-price from `price`/`compareAtPrice`/`perUnit` |

## Tips

- Always pair with [BuyBox](/islands/commerce/buy-box) (`listenForEvents:true`) — PlanSelector selects, it does not add to cart
- Never combine with [SubscriptionToggle](/islands/commerce/subscription-toggle) on the same page (both emit `subscription:changed`)
- Use `static:true` on a one-time plan whose price should not follow tier selection
- Selection state is exposed as `data-selected="true"` on the card — restyle it entirely from section CSS, no JS needed
- For tier-driven re-pricing pass pre-computed price strings in the QuantityBreaks tiers (`subscribePrice` etc.) — islands do no money math

## Stylable Parts

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

| Part | Description |
|------|-------------|
| `root` | Outermost radiogroup container |
| `plan` | One plan card (carries `data-selected="true"` when active) |
| `plan-radio` | The radio circle |
| `plan-title` | Plan title text |
| `plan-price` | Current price |
| `plan-compare` | Compare-at strikethrough |
| `plan-per-unit` | Per-serving / per-unit line |
| `plan-benefits` | Benefit checklist container |
| `benefit-item` | One benefit row |
| `cadence-line` | Auto-ship cadence row (from tiers:changed) |
| `plan-note` | Secondary note under the title |
| `plan-badge` | Floating badge chip |
| `plan-slot` | Container-slot area inside the first card |

```css
/* Example: re-theme the selected card */
[data-part="plan"][data-selected="true"] {
  border-color: var(--lx-accent-color);
  background: var(--lx-surface-alt);
}
```
