01 · sections/bundle-progress.liquid
Tiered bundle bar
Shipped on Winona: 2 items unlock 10% off, 3 unlock 15%, 4 unlock 20%. The bar sits above the header on every page, so the deal is visible before the cart opens.
- Always shows the next reward, not just the current one
- Tiers live in section blocks, editable without a deploy
- Same markup on first paint and after every cart update
Press + and − to change the cart
Build your own bundle and save
Add 1 more item to unlock 10% off
Demo data, not the live store
{%- liquid
assign count = cart.item_count
assign discount = 0
assign next_tier = nil
for block in section.blocks
if count >= block.settings.min_items
assign discount = block.settings.percent_off
elsif next_tier == nil
assign next_tier = block
endif
endfor
assign fill = count | minus: 1 | at_least: 0 | times: 100 | divided_by: 3
-%}
<bundle-progress
data-section-id="{{ section.id }}"
data-discount="{{ discount }}"
style="--fill: {{ fill | at_most: 100 }}%"
>
{%- if next_tier -%}
{%- assign left = next_tier.settings.min_items | minus: count -%}
{{ 'bundle.unlock' | t: count: left, percent: next_tier.settings.percent_off }}
{%- else -%}
{{ 'bundle.best_price' | t }}
{%- endif -%}
</bundle-progress>// Re-renders the bar through the Section Rendering API
// whenever the cart changes, so tiers stay merchant-editable.
class BundleProgress extends HTMLElement {
connectedCallback() {
document.addEventListener('cart:updated', this.refresh);
}
disconnectedCallback() {
document.removeEventListener('cart:updated', this.refresh);
}
refresh = async () => {
const url = `${Shopify.routes.root}?section_id=${this.dataset.sectionId}`;
const html = await (await fetch(url)).text();
const next = new DOMParser()
.parseFromString(html, 'text/html')
.querySelector('bundle-progress');
if (next) this.replaceWith(next);
};
}
customElements.define('bundle-progress', BundleProgress);






