# Cart Profiles

> Configure, publish, and target reusable cart profiles.

Canonical URL: https://c22dfdd27662:3005/guides/cart-v2

Cart profiles are reusable, versioned cart experiences. A store can keep
multiple profiles for different merchandising strategies, publish stable
versions, choose a store default, and assign a profile to a page or campaign.

Open <AppLink to="cart">Storefront → Cart</AppLink> to create, preview,
publish, assign, and review cart profiles.

To build a module that reads cart state, sends commands or changes visibility,
see [Custom Cart Modules](/guides/custom-cart-modules).

## Page and cart ownership

The page and cart are separate:

- Page sections own the storefront content.
- Cart V2 is enabled by default and cannot be disabled.
- A cart profile owns the drawer HTML, module order, rules, offers, behavior,
  responsive mode, and cart-only styling.
- The renderer resolves and injects the cart after the page sections.
- Page titles, SEO metadata, and page section HTML never select a cart profile.

Do not add `DrawerShell`, `CartLines`, or other cart islands to page source.
Publish validation rejects an inline cart.

## Resolution order

The renderer selects one published profile for every cart-profile page:

```text
page assignment > campaign assignment > store default > legacy fallback
```

A page assignment is the most specific. Removing it restores the next
available fallback instead of disabling the cart.

Only published, non-archived profiles can become defaults or assignments.
Editing a profile creates draft changes. The live cart remains on its current
published snapshot until the merchant publishes through MCP or from
<AppLink to="cart">Storefront → Cart</AppLink>.

Changing an assignment, default, published profile version, or archive state
automatically refreshes the affected published Shopify pages after
the database change commits. You do not need to republish each page manually.

## Default cart behavior

New page source does not need a Cart V2 flag. Configure and target the cart
profile separately. The compiler continues accepting `use_cart_v2:true` on
older pages as a deprecated compatibility field. It rejects
`use_cart_v2:false` because Cart V2 cannot be disabled.

## How a cart opens

Cart triggers and cart presentation communicate through the shared
`cart:open` event:

1. The renderer injects the resolved profile's `DrawerShell` after the page
   sections.
2. `BuyBox`, `QuickAdd`, `StickyBar`, product cards, and offer cards add the
   selected variant through the shared cart action.
3. The purchase control shows its own feedback while the mutation runs.
4. The renderer defaults connected stores to `optimistic-v1` without rollout
   flags or a store allowlist. In this runtime, acceptance emits
   `cart:open` immediately and projects the requested items into shared state.
   Existing experiment responses and captured legacy artifacts retain their
   existing behavior.
5. The storefront hydrator bridges a DOM `cart:open` event to the shared cart
   event bus.
6. The injected `DrawerShell` listens for `cart:open`, opens, and hydrates its
   child cart islands on first use.
7. Shopify responses reconcile the cart. Rejected changes roll back while
   accepted items remain. Errors appear in the drawer and purchase control;
   an open drawer does not itself mean Shopify confirmed the add.

The optimistic runtime accepts quantity edits and removals while Shopify is
busy. Its subtotal is explicitly estimated until confirmation; discounts and
the final total update after Shopify and offers settle. Checkout waits for the
latest cart and then navigates automatically, with a cancel action while waiting.
Custom sections must not add their own Shopify requests, optimistic totals,
retry loops, or delayed drawer-opening logic.

The site header cart button uses the same `cart:open` event. A custom
storefront control can open the cart with:

```js
section.dispatchEvent(new CustomEvent("cart:open", { bubbles: true }))
```

The trigger does not need to know which profile is assigned. Profile resolution
happens before hydration, so every valid trigger opens the cart already
selected for that page.

The header cart button and badge subscribe to the same cart state as the
drawer. A successful add must therefore be verified in two separate steps:
the Shopify/cart mutation succeeds, and the configured drawer becomes visibly
open with the correct variant, quantity, price, and image.

## Custom cart interactions and batch add

Custom swipe decks, quizzes, configurators, comparisons, wishlists,
calculators, guided-selling sections, and other custom interactions can add a
dynamic set of products through the renderer-managed `lx:cart:add-items`
command. Do not hide BuyBoxes or BundleBuilders, call `.click()`, query the
global document, call Shopify directly, or author cart drawer markup.

The initiating element must use `data-lx-control` and a registered lifecycle
handler:

```js
const addButton = lifecycle.query('[data-lx-control="add-selection"]');

addButton.addEventListener("click", () => {
  section.dispatchEvent(
    new CustomEvent("lx:cart:add-items", {
      bubbles: true,
      detail: {
        requestId: crypto.randomUUID(),
        items: selectedProducts.map((product) => ({
          variantId: product.variantId,
          quantity: product.quantity,
          sellingPlanId: product.sellingPlanId ?? null,
          attributes: product.attributes ?? {},
          meta: {
            title: product.title,
            variantTitle: product.variantTitle,
            image: product.image,
            priceAmount: product.priceAmount,
            currencyCode: product.currencyCode,
          },
        })),
        openCart: true,
      },
    }),
  );
});
```

### Payload limits

| Field | Contract |
|---|---|
| `requestId` | Required unique string, maximum 128 characters |
| `items` | 1–50 entries |
| `variantId` | `gid://shopify/ProductVariant/<numeric-id>` |
| `quantity` | Integer from 1–99 after compatible duplicates are combined |
| `sellingPlanId` | Optional `null` or Shopify SellingPlan GID |
| `attributes` | Up to 20 string pairs; keys up to 64 and values up to 255 characters |
| `meta` | Optional selected-product display hints; decimal `priceAmount` and `currencyCode` must be supplied together |
| `openCart` | Optional boolean, defaults to `true` |

Lines combine only when variant, selling plan, and normalized attributes all
match. Different selling plans or attributes remain separate lines.

### Response events

The renderer dispatches bubbling responses from the originating section:

- `lx:cart:add-items:pending`
- `lx:cart:add-items:accepted` — optimistic intent queued; release local controls
- `lx:cart:add-items:confirmed` — Shopify accepted the merchandise
- `lx:cart:add-items:success`
- `lx:cart:add-items:error`

Every response includes `requestId`. Success includes `addedLines`, the
settled current `cart`, and warnings. Error includes a safe `code`, `message`,
`failedItems`, and the current cart when Shopify changed it.

The accepted and confirmed events are available in the optimistic runtime.
Use accepted for responsive controls and confirmed for success copy; keep
success/error listeners for legacy runtimes. Accepted does not confirm a
purchase or price. Display hints must match the selected market and selling
plan. They are never sent as Shopify prices, and missing data uses placeholders.

`requestId` is idempotent for the browser-tab session. An identical retry
replays the prior response without another mutation. Reusing the ID for a
different payload returns `request_id_conflict`.

The renderer performs one `cartCreate` or `cartLinesAdd` request for the
normalized array. If Shopify adds less than requested, Lexsis preserves the
resulting cart, emits `partial_add`, and does not open the drawer or attempt a
second rollback mutation. A fully successful request settles configured
rewards and promotions, then opens the effective Cart V2 drawer when
`openCart` is true.

Common error codes include `invalid_request`, `invalid_request_id`,
`invalid_variant_id`, `invalid_quantity`, `invalid_selling_plan_id`,
`invalid_attributes`, `cart_profile_unavailable`,
`cart_runtime_unavailable`, `unavailable_variant`, `shopify_rejected`,
`partial_add`, `mutation_failed`, and `request_id_conflict`.

## Missing profile behavior

A commerce page with purchase controls but no effective profile is invalid.
Preview marks the page with `data-lx-cart-profile-state="missing"` and reports:

```text
This preview has purchase controls but no effective Cart V2 profile.
```

Fix the store default or page/campaign assignment before publishing. Do not
work around the warning by manually embedding DrawerShell or cart child
islands in page source.

## Profile configuration

A profile can configure:

- Desktop and mobile cart modes
- Drawer width and motion
- Visual presets, colors, typography, shape, spacing, and responsive overrides
- Line-item layout, media, quantity controls, removal controls, and sale prices
- Module order and enabled modules
- Free-shipping threshold and celebration
- Shopify selling-plan presentation
- Manual and Shopify-powered product offers
- Cumulative reward milestones, automatic gifts, coupons, and payment logos
- Conditional cart rules
- Checkout behavior
- Scoped custom CSS

The required layout modules are cart lines, order summary, and checkout.
Shipping progress, subscription presentation, payment methods, and product
offer modules can be enabled, disabled, and reordered.

## Cart design contract

Every profile has a `CartDesignSpecV1` document:

```json
{
  "schema_version": 1,
  "preset_id": "clean-default",
  "tokens": {},
  "shell": {},
  "modules": {},
  "responsive": {}
}
```

The three built-in presets are:

| Preset | Intended result |
|---|---|
| `clean-default` | Balanced default with standard spacing and controls |
| `soft-beauty` | Wider editorial layout, softer surfaces, larger media, and brand typography |
| `compact-utility` | Narrow, dense cart with compact line items and controls |

The compiler resolves the selected preset first, then applies explicit
overrides. The saved draft keeps the authored specification while preview and
production use the deterministic compiled result.

### Design tokens

`tokens` controls:

- Colors: background, surfaces, text, accent, border, success, warning, error,
  reward, and coupon
- Typography: brand/body/heading family, title and body scale, and weight
- Shape: radius, control radius, border width, and shadow
- Spacing: density and section gap

### Shell and module design

`shell` controls width, header layout, title, item-count treatment,
close-button style, sticky header/footer, and content background.

`modules` can separately style reward progress, free shipping, cart lines,
coupon summary, product offers, order summary, checkout, payment options, and
the empty state. Cart-line design also controls media shape and size, dividers,
quantity and removal controls, sale-price treatment, and free-gift appearance.

### Responsive overrides

Use `responsive.mobile`, `responsive.tablet`, and `responsive.desktop` to
override tokens, shell settings, or module design at a specific viewport.
Responsive values merge over the selected preset and base profile design.

## Preview states

The Cart editor can preview real layout states without publishing:

- One-time purchase
- Subscription purchase
- Shipping reward unlocked
- Empty cart
- Error state

Preview uses the same compiled design contract and cart runtime as published
pages. Verify each applicable state on mobile and desktop before publishing.

## Cart module catalog

These are renderer-managed building blocks selected by the effective profile:

| Island | Responsibility |
|---|---|
| `DrawerShell` | Responsive drawer, sheet, modal, fullscreen, or dropdown container |
| `CartLines` | Product image, variant, quantity, removal, selling plans, and reward-line state |
| `CartSummary` | Subtotal and optional discounts, shipping, and tax rows |
| `CartCheckoutButton` | Checkout CTA, secondary CTA, item count, and payment providers |
| `CartProgressBar` | Simple free-shipping or tier progress |
| `CartRewardProgress` | Cumulative gifts, fixed-price products, discounts, and shipping rewards |
| `CartCoupons` | Curated offers, applied savings, coupon drawer, and manual code entry |
| `CartDiscountInput` | Direct discount-code application |
| `CartCrossSell` | Reactive profile-configured recommendations |

These island references document profile capabilities and styling contracts.
They are not instructions to add cart markup to storefront page source.

## Offers and subscriptions

Offer slots support these placements:

- `header`
- `after_line`
- `after_lines`
- `before_checkout`

An offer can use manually selected Shopify products or Shopify `RELATED` and
`COMPLEMENTARY` recommendations. Product IDs must be Shopify product GIDs.

Subscribe and Save is a line-item capability. It appears only when the product
or variant has real Shopify selling plans. Profiles can control whether the
purchase options are shown and whether they use cards or a select menu, but a
profile cannot create selling-plan eligibility.

## Profile styling

Brand CSS variables remain available to the cart. Ordinary page selectors,
including page `data-part` overrides, are excluded from the cart root and its
children. Profile design settings and `custom_css` own cart styling. See
[Cart Styling Hooks](/guides/cart-styling) for all shell/island parts, stable
module IDs and inline styles.

Custom CSS is:

- Stored with the profile draft
- Sanitized before saving
- Scoped under the resolved cart profile root
- Shared by preview and production rendering
- Published only when the merchant publishes the draft

External CSS imports, external `url()` values, script escapes, and unbalanced
rules are rejected.

Example:

```css
[data-part="panel"] {
  border-radius: 0;
}

[data-part="checkout"] {
  font-weight: 600;
}
```

## MCP workflow

For draft compilation without saving, named CSS blocks, and six signed sample
cart states, see [Preview and Verify Cart Edits](/guides/cart-edit-loop).
The MCP returns URLs and static checks; visual inspection happens in the
caller's browser. Sample carts never restore or overwrite a shopper cart.

The MCP supports profile creation/duplication, design and promotion drafts,
previews, approved publication, forward-draft rollback, defaults, page/campaign
assignments, archive with reassignment, and audit history. Readiness and version
checks run through the same backend as the app. Shopify discount writes happen
only at publish. Rollback creates a draft; review and publish it separately.

See [Cart Lifecycle Through MCP](/guides/cart-lifecycle) for the complete cycle,
patch semantics, approval boundaries and failure recovery.

See [Cart Profile Tools](/tools/cart) for complete request examples.

## Migrating a legacy page

1. Remove `CartDrawer` and any inline `DrawerShell` from page sections.
2. Remove the obsolete `use_cart_v2` field from newly authored source.
3. Publish or select a cart profile in the Lexsis app.
4. Assign it to the page, or rely on the store default.
5. Verify add-to-cart and header cart controls both open the resolved profile.
6. Publish profile changes after previewing the draft.

## Related

- [Cart Profile Tools](/tools/cart)
- <AppLink to="cart">Open Cart profiles in the Lexsis app</AppLink>
- [DrawerShell](/islands/cart/drawer-shell)
- [Cart Lines](/islands/cart/cart-lines)
- [Quick Add](/islands/commerce/quick-add)
- [Sticky Bar](/islands/commerce/sticky-bar)
