> For the complete documentation index, see [llms.txt](https://analog.groupthought.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://analog.groupthought.com/reference/alpine/cart-item.md).

# x-cart-item

Directives and a magic for changing or removing one cart line item with fetch.

Directives and a magic for changing or removing one cart line item with fetch.

| Directive / magic                   | What it does                                                                                                                                |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `x-cart-item="{{ forloop.index }}"` | The root, on the item's `<form>` or a wrapper of one. The expression is required: the item's 1-based line number                            |
| `x-cart-item.wait-error.3000ms`     | Modifiers pass through to `x-async-form`: `wait-success` (default 1000ms) and `wait-error`                                                  |
| `x-cart-item:context`               | Holds the item state. The root binds it for you                                                                                             |
| `x-cart-item:form`                  | The form element. Sets `method="POST"`. The root binds it for you                                                                           |
| `$cartItem.quantity`                | The quantity to submit. Read and write it, or bind it with `x-model`                                                                        |
| `$cartItem.isRemoving`              | `true` while the quantity is `0`                                                                                                            |
| `$cartItem.remove()`                | Sets the quantity to `0` and submits                                                                                                        |
| `$cartItem` (the rest)              | The same API as [`$asyncForm`](/reference/alpine/async-form.md): `state`, `errorMessage`, `errorDescription`, `submitAsync()`, `reset()`, … |

Submission goes to `/cart/change.js` (`window.theme.routes.cartChange`) with the line key from `data-line-key` (or the line number without one), the quantity, and the ids of every section marked [`data-cart-api-section`](/reference/alpine/cart-api-section.md), so the response includes their updated HTML. The cart re-renders without a page reload.

After each response the plugin sets `$cartItem.quantity` to the quantity the cart really holds. It finds the line by its key, so a removed line keeps quantity `0` when the next line moves into its position. A stockout returns a 422 without that quantity, so the plugin fetches the cart again. The input returns to the available amount.

Before removal, the plugin moves focus to a neighboring item. Otherwise, hiding the focused row would drop keyboard focus to `<body>`.

On top of the [`x-async-form` events](/reference/alpine/async-form.md), the plugin dispatches the standard `shopify:cart:lines-update` event (action `'update'`, or `'remove'` when the quantity hits 0) before the request, with `event.promise` resolving to the cart and the theme detail, and `shopify:cart:error` on failure. See [Events](/developer-platform/events.md). Give the element `data-line-key="{{ line_item.key }}"`. The request then targets the line by key, which stays on this line while another request removes an earlier line and shifts the line numbers, and the event can identify the changed line.

## Examples

### A basic line item

Use `x-cart-item` with the line number, bind the input to `$cartItem.quantity`, and submit on input:

```liquid
{% for item in cart.items %}
  <form x-cart-item="{{ forloop.index }}">
    <input
      type="number"
      name="updates[]"
      value="{{ item.quantity }}"
      x-model.fill.number="$cartItem.quantity"
      @input.debounce.500ms="$cartItem.submitAsync()"
    >
  </form>
{% endfor %}
```

The debounce collapses fast typing into one request, and a new submission cancels the pending one.

### A quantity stepper that saves on change

Use the [x-stepper](/reference/alpine/stepper.md) `increment` and `decrement` events to submit each step. The cart line item uses its stepper this way:

```html
<div
  x-stepper
  @increment.debounce.500ms="if ($refs.quantityInput.checkValidity()) await $cartItem.submitAsync()"
  @decrement.debounce.500ms="if ($refs.quantityInput.checkValidity()) await $cartItem.submitAsync()"
>
  <button type="button" x-stepper:minus>−</button>
  <input
    type="number"
    min="0"
    x-stepper:input
    x-ref="quantityInput"
    x-model.fill.number.lazy="$cartItem.quantity"
    @blur="if ($el.checkValidity()) $cartItem.submitAsync()"
  >
  <button type="button" x-stepper:plus>+</button>
</div>
```

### A remove button

Use `$cartItem.remove()` on a button and `$cartItem.isRemoving` to hide the row while the removal runs:

```html
<li x-cart-item="{{ forloop.index }}" x-show="$cartItem.isRemoving === false">
  …
  <button type="button" @click.prevent="$cartItem.remove()">Remove</button>
</li>
```

### Showing a stockout

Use the `wait-error` modifier to clear the message automatically. The cart line item shows the server's stock message for three seconds:

```liquid
<li x-cart-item.wait-error.3000ms="{{ line_item.index | plus: 1 }}">
  <p role="alert" x-show="$cartItem.state === 'error'" x-cloak
     x-html="$cartItem.errorDescription || $cartItem.errorMessage"></p>
  …
</li>
```

The quantity input snaps back to the amount the cart really holds, so it never shows a number the shop cannot sell.

## Customizing

The cart line item's layout, badges, and quantity controls are described in [Cart](/features/cart.md). Use [x-cart-form](/reference/alpine/cart-form.md) to update the note or several `updates[]` values in one request.
