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

# Commerce attributes

> Add to cart, variants, the cart drawer, coupons and checkout from plain Liquid or Vite markup.

Liquid and Vite sections do not need JavaScript for shopping. Add `data-jm-*` attributes to your markup and the platform handles clicks, cart state and checkout. React sections can use the same attributes, or `useCart()` from `@/platform`.

## Attributes

| Attribute | Action |
| - | - |
| `data-jm-add="<product id or slug>"` | Add to cart. Variant and quantity come from the closest `[data-jm-product]`. |
| `data-jm-stay` (with `data-jm-add`) | Add without opening the cart. |
| `data-jm-buy-now` (with `data-jm-add`) | Start checkout right away. |
| `data-jm-product` | Wraps one product's controls: variant chips, variant input, quantity. |
| `data-jm-variant-pick="<variant id>"` | Variant chip inside `[data-jm-product]`. Sets `aria-pressed` on the selected chip. |
| `input[name=variant]` | Variant id inside `[data-jm-product]`. |
| `data-jm-qty-input` | Quantity input inside `[data-jm-product]`. |
| `data-jm-cart-open` / `data-jm-cart-close` / `data-jm-cart-toggle` | Open, close or toggle the cart drawer. |
| `data-jm-cart-inc` / `data-jm-cart-dec` / `data-jm-cart-remove="<line id>"` | Change or remove a cart line. |
| `data-jm-checkout` | Check out with the current cart. |
| `form[data-jm-coupon-form]` with `input[name=coupon]` | Apply a coupon. |
| `data-jm-coupon-remove` | Remove the coupon. |

The cart's open state is in `cart.open` and on `html[data-jm-cart-open]`, so CSS can style the drawer.

## Product card example

```liquid snippets/product-card.liquid theme={null}
<div data-jm-product>
  <a href="{{ product.url | url }}">
    <img src="{{ product.image | image_url }}" alt="{{ product.title }}" />
    <h3>{{ product.title }}</h3>
  </a>
  <p>{{ product.price | money }}</p>

  {%- if product.variants.size > 1 -%}
    {%- for variant in product.variants -%}
      <button type="button" data-jm-variant-pick="{{ variant.id }}">{{ variant.label }}</button>
    {%- endfor -%}
  {%- endif -%}

  <input data-jm-qty-input type="number" min="1" value="1" />

  {%- if product.on_hold or product.sold_out -%}
    <button type="button" disabled>Unavailable</button>
  {%- else -%}
    <button type="button" data-jm-add="{{ product.id }}">Add to cart</button>
    <button type="button" data-jm-add="{{ product.id }}" data-jm-buy-now>Buy now</button>
  {%- endif -%}
</div>
```

## Cart drawer example

```liquid src/sections/cart-drawer/index.liquid theme={null}
<aside class="cart-drawer">
  <button type="button" data-jm-cart-close>Close</button>
  {%- for item in cart.items -%}
    <div>
      <span>{{ item.title }}</span>
      <button type="button" data-jm-cart-dec="{{ item.id }}">−</button>
      <span>{{ item.quantity }}</span>
      <button type="button" data-jm-cart-inc="{{ item.id }}">+</button>
      <span>{{ item.line_price | money }}</span>
      <button type="button" data-jm-cart-remove="{{ item.id }}">Remove</button>
    </div>
  {%- endfor -%}

  {%- if cart.coupons_enabled -%}
    <form data-jm-coupon-form>
      <input name="coupon" placeholder="Coupon" />
      <button type="submit">Apply</button>
    </form>
  {%- endif -%}

  <p>Total: {{ cart.total | money }}</p>
  <button type="button" data-jm-checkout>Checkout</button>
</aside>
```

```css src/theme.css theme={null}
.cart-drawer { display: none; }
html[data-jm-cart-open] .cart-drawer { display: block; }
```

> [!WARNING]
>
> Products on hold (`on_hold`) must not be buyable. Disable their buttons. The platform also refuses to add them to the cart.

## JavaScript API

For custom behavior, `window.JoinMarkt` exposes:

| Call | Does |
| - | - |
| `JoinMarkt.cart.add(id, { variant, quantity, open })` | Add a product (id or slug). Returns `false` when it cannot be added. |
| `JoinMarkt.cart.setQuantity(lineId, n)` / `.remove(lineId)` | Change or remove a line. |
| `JoinMarkt.cart.open()` / `.close()` / `.toggle()` | Cart drawer state. |
| `JoinMarkt.cart.applyCoupon(code)` / `.removeCoupon()` | Coupons. `applyCoupon` resolves to `true` on success. |
| `JoinMarkt.cart.checkout()` | Start checkout with the cart. |
| `JoinMarkt.cart.items`, `.count`, `.subtotal`, `.discount`, `.total` | Current cart values. |
| `JoinMarkt.formatPrice(n)` | Format a price in the display currency. |
| `JoinMarkt.navigate(path)` | Go to a store page without a full reload. |

Events on `document`:

| Event | Detail |
| - | - |
| `jm:cart` | The cart after every change: `items`, `count`, `total`, `open`. |
| `jm:checkout-error` | `detail.message` explains why checkout could not start. |

```ts src/theme.ts theme={null}
document.addEventListener('jm:cart', (event) => {
  const cart = (event as CustomEvent).detail
  document.title = cart.count > 0 ? `(${cart.count}) Store` : 'Store'
})
```


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