Skip to main content
The elements never generate markup. You write the HTML; the elements fill it in through a handful of data-qb-* attributes and reflect their state as attributes you can style.

Bindings

Put these on any element inside a component. Values are paths into the component’s scope, never expressions: there is no evaluator and nothing in a template can execute. Paths walk dots and array indices (product.images.0.url). A missing branch resolves to undefined and renders empty instead of throwing, so a template written for a product with images does not break on one without.
Write hidden in the markup on anything with data-qb-show, or it is visible for a moment before the first bind:

Which element is bound

A component’s own attributes are configuration; bindings apply to its descendants. Scope-owning elements (<qb-product>, <qb-product-list>, <qb-options>, <qb-option-values>, <qb-cart>, <qb-cart-items>, <qb-order-confirmation>) stop the walk, so a binding written on one of them itself is never applied. The other elements (<qb-add-to-cart>, <qb-product-image>, <qb-checkout-button>, <qb-checkout>, <qb-cart-count>, <qb-seo>) are bound by the enclosing component like any element.
Nothing inside a <template> is ever bound in place; only its clones are.

Repeats

Every list element (<qb-product-list>, <qb-options>, <qb-option-values>, <qb-cart-items>) clones its <template> once per item. Three rules decide whether a list renders at all:
1

The template is a direct child of the repeating element

The element scans its own children for the <template>. A template nested inside a <ul> or a <div> is never found; the element warns once in the console and renders nothing.
2

Rows are inserted after the template, as siblings

So the repeating element is the list container. Style qb-product-list itself as the grid and give the template a root of <article>, <a> or <div>. A heading or empty-state paragraph written before the template stays where it is.
3

A template may have several root elements

<dt> and <dd>, or two <td>s, all belong to the same item.
Rows are keyed (product id, option group id, option value id, cart line id) and reused across updates. That keeps a cart quantity <input> focused while its line re-renders, and keeps a swatch’s DOM stable across selection changes.

Actions

One attribute, data-qb-action, resolved by the nearest enclosing component that knows the verb. Both click and change dispatch it, so a <select> or a quantity <input> can carry one. On an <a> or a submit button the default is prevented. A verb nothing handles is reported once in the console on a click, so a typo is a message instead of a button that silently does nothing.

Quantity for add-to-cart

In precedence order:
  1. data-qb-quantity="3" on the clicked element.
  2. The nearest input[data-qb-quantity-input] inside <qb-add-to-cart> (for a plain data-qb-action="add-to-cart" button, the search runs from the click up to <qb-product>).
  3. The quantity attribute of <qb-add-to-cart>.
  4. 1.
  • A quantity input placed next to the wrapper rather than inside it is ignored.
  • data-qb-quantity on a <button> is missed when the click lands on an icon inside it; prefer the input.
  • A blank or 0 input counts as 1.
  • In cart rows the attribute is different: input[data-qb-quantity] (no suffix) is the bound quantity of an existing line and commits on change, so typing 12 does not first send 1. 0 removes the line.

Styling on reflected state

The elements ship no CSS. Everything they know is reflected as an attribute, so a loading skeleton, a dimmed swatch or a disabled checkout button is a stylesheet’s job:
Never write disabled into an option value template. Unavailable values must stay clickable: clicking one keeps the new choice and clears whatever contradicts it. Dim them with [data-available="false"] instead.

Money

price.display, item.lineTotalFormatted, cart.totalFormatted and friends are localized strings, because an HTML binding cannot leave formatting to the app the way React does. They format with the currency the response states (product.currency, cart.currency), so the right symbol follows a currency switch with nothing configured, and with the configured locale. The configured currency is only a fallback for a platform older than product.currency (see Set the currency). The raw minor-unit integers are always in scope too (price.amount, item.lineTotal, cart.total) for your own formatter. Quickbutik.formatMoney(129900, "SEK", { locale: "sv-SE" }) is the formatter the elements use ("1 299,00 kr").

Scope paths

The full list of what each component puts in scope is in the elements reference. The ones you will use most: