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.
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.
<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.<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:data-qb-quantity="3"on the clicked element.- The nearest
input[data-qb-quantity-input]inside<qb-add-to-cart>(for a plaindata-qb-action="add-to-cart"button, the search runs from the click up to<qb-product>). - The
quantityattribute of<qb-add-to-cart>. 1.
- A quantity input placed next to the wrapper rather than inside it is ignored.
data-qb-quantityon a<button>is missed when the click lands on an icon inside it; prefer the input.- A blank or
0input 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 onchange, so typing12does not first send1.0removes 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: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").