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. |
cart.open and on html[data-jm-cart-open], so CSS can style the drawer.
Product card example
snippets/product-card.liquid
<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
src/sections/cart-drawer/index.liquid
<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>
src/theme.css
.cart-drawer { display: none; }
html[data-jm-cart-open] .cart-drawer { display: block; }
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. |
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. |
src/theme.ts
document.addEventListener('jm:cart', (event) => {
const cart = (event as CustomEvent).detail
document.title = cart.count > 0 ? `(${cart.count}) Store` : 'Store'
})

