Gift Wrap Checkbox
A paid gift wrap add-on with an optional gift message, right above add to cart.
Hand-picked product bundles with checkboxes, a running total and one add-all button.
Important: Before making any changes to your theme, duplicate it in Online Store > Themes so you have a backup.
A frequently bought together block puts the obvious next purchase in front of the shopper while they're still deciding. This version lets you hand-pick up to four products, shows a live total, and adds everything that's ticked to the cart in one click.
Frequently bought together
Preview with the default settings. Colours, text and layout can all be changed in the theme editor.
In Shopify admin go to Online Store > Themes. Next to the theme you want to edit, click the three dots and choose Edit code.
In the Sections folder click Add a new section, name it lt-frequently-bought-together, delete the placeholder code, paste in the code below and click Save.
Click Customize, go to Products > Default product, click Add section and choose Bought together.
Change the settings in the sidebar, drag the section where you want it, and click Save.
{%- comment -%}
Frequently bought together (hand-picked products) by LAUNCHTIP
https://launchtip.com/free-shopify-sections-code
{%- endcomment -%}
{%- liquid
assign picks = section.settings.products
assign show_current = false
if section.settings.include_current and product != blank
assign show_current = true
endif
assign total = 0
assign count = 0
-%}
{%- style -%}
#lt-{{ section.id }} { padding: {{ section.settings.padding }}px 0; }
#lt-{{ section.id }} .lt-fbt { border: 1px solid {{ section.settings.border_color }}; border-radius: 10px; padding: 20px; }
#lt-{{ section.id }} .lt-fbt__heading { margin: 0 0 14px; }
#lt-{{ section.id }} .lt-fbt__item { display: flex; align-items: center; gap: 12px; padding: 10px 0; border-top: 1px solid {{ section.settings.border_color }}; }
#lt-{{ section.id }} .lt-fbt__item:first-of-type { border-top: 0; }
#lt-{{ section.id }} .lt-fbt__item input { width: 18px; height: 18px; accent-color: {{ section.settings.button_color }}; flex-shrink: 0; }
#lt-{{ section.id }} .lt-fbt__item img { width: 56px; height: 56px; object-fit: cover; border-radius: 6px; flex-shrink: 0; }
#lt-{{ section.id }} .lt-fbt__name { flex: 1; min-width: 0; }
#lt-{{ section.id }} .lt-fbt__name a { color: inherit; text-decoration: none; }
#lt-{{ section.id }} .lt-fbt__tag { display: block; font-size: 0.8em; opacity: 0.6; }
#lt-{{ section.id }} .lt-fbt__price { font-weight: 600; white-space: nowrap; }
#lt-{{ section.id }} .lt-fbt__footer { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; margin-top: 16px; }
#lt-{{ section.id }} .lt-fbt__total { font-size: 1.1em; }
#lt-{{ section.id }} .lt-fbt__button { background: {{ section.settings.button_color }}; color: {{ section.settings.button_text }}; border: 0; border-radius: 6px; padding: 12px 20px; font: inherit; font-weight: 600; cursor: pointer; }
#lt-{{ section.id }} .lt-fbt__button[disabled] { opacity: 0.5; cursor: default; }
{%- endstyle -%}
<div
id="lt-{{ section.id }}"
class="page-width"
data-add-url="{{ routes.cart_add_url }}.js"
data-cart-url="{{ routes.cart_url }}"
data-currency="{{ cart.currency.iso_code }}"
data-after-add="{{ section.settings.after_add }}"
>
<div class="lt-fbt">
{%- if section.settings.heading != blank -%}
<h2 class="lt-fbt__heading h3">{{ section.settings.heading }}</h2>
{%- endif -%}
{%- for i in (0..4) -%}
{%- liquid
if i == 0
unless show_current
continue
endunless
assign p = product
else
assign idx = i | minus: 1
assign p = picks[idx]
if p == blank
continue
endif
if show_current and p.id == product.id
continue
endif
endif
assign v = p.selected_or_first_available_variant
unless v.available
continue
endunless
assign total = total | plus: v.price
assign count = count | plus: 1
-%}
<label class="lt-fbt__item" {% if i == 0 %}data-current{% endif %}>
<input type="checkbox" checked data-id="{{ v.id }}" data-price="{{ v.price }}">
{%- if p.featured_image != blank -%}
{{ p.featured_image | image_url: width: 160 | image_tag: loading: 'lazy', alt: p.title }}
{%- endif -%}
<span class="lt-fbt__name">
{%- if i == 0 -%}
<span class="lt-fbt__tag">{{ section.settings.current_label }}</span>
{{ p.title }}
{%- else -%}
<a href="{{ p.url }}">{{ p.title }}</a>
{%- endif -%}
</span>
<span class="lt-fbt__price">{{ v.price | money }}</span>
</label>
{%- endfor -%}
{%- if count == 0 and request.design_mode -%}
<p>Pick up to 4 products in the section settings.</p>
{%- endif -%}
{%- if count > 0 -%}
<div class="lt-fbt__footer">
<span class="lt-fbt__total">{{ section.settings.total_label }} <strong data-total>{{ total | money }}</strong></span>
<button type="button" class="lt-fbt__button" data-label="{{ section.settings.button_label | escape }}">{{ section.settings.button_label }}</button>
</div>
{%- endif -%}
</div>
{%- if show_current -%}
<script type="application/json" data-variants>
{ {%- for v in product.variants -%}"{{ v.id }}": { "price": {{ v.price }}, "available": {{ v.available }} }{%- unless forloop.last -%},{%- endunless -%}{%- endfor -%} }
</script>
{%- endif -%}
</div>
<script>
(function () {
var root = document.getElementById('lt-{{ section.id }}');
if (!root || root.dataset.ready) return;
root.dataset.ready = '1';
var button = root.querySelector('.lt-fbt__button');
var totalEl = root.querySelector('[data-total]');
if (!button) return;
var variantsEl = root.querySelector('[data-variants]');
var variants = variantsEl ? JSON.parse(variantsEl.textContent) : {};
function money(cents) {
var currency = (window.Shopify && Shopify.currency && Shopify.currency.active) || root.dataset.currency;
return new Intl.NumberFormat(document.documentElement.lang || undefined, { style: 'currency', currency: currency }).format(cents / 100);
}
function checked() {
return Array.prototype.filter.call(root.querySelectorAll('input[data-id]'), function (input) { return input.checked; });
}
function update() {
var picked = checked();
var total = picked.reduce(function (sum, input) { return sum + Number(input.dataset.price); }, 0);
totalEl.textContent = money(total);
button.disabled = picked.length === 0;
}
root.addEventListener('change', update);
var current = root.querySelector('[data-current] input');
var form = document.querySelector('main form[action*="/cart/add"]') || document.querySelector('form[action*="/cart/add"]');
if (current && form) {
form.addEventListener('change', function () {
var idInput = form.querySelector('[name="id"]');
var variant = idInput && variants[idInput.value];
if (!variant) return;
current.dataset.id = idInput.value;
current.dataset.price = variant.price;
current.disabled = !variant.available;
if (!variant.available) current.checked = false;
update();
});
}
button.addEventListener('click', function () {
var items = checked().map(function (input) { return { id: Number(input.dataset.id), quantity: 1 }; });
if (!items.length) return;
button.disabled = true;
fetch(root.dataset.addUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify({ items: items })
})
.then(function (response) {
if (!response.ok) throw new Error('add failed');
if (root.dataset.afterAdd === 'cart') {
window.location.href = root.dataset.cartUrl;
return;
}
button.textContent = {{ section.settings.added_label | json }};
document.dispatchEvent(new CustomEvent('cart:refresh', { bubbles: true }));
setTimeout(function () { button.textContent = button.dataset.label; button.disabled = false; }, 2500);
})
.catch(function () {
button.textContent = button.dataset.label;
button.disabled = false;
});
});
})();
</script>
{% schema %}
{
"name": "Bought together",
"tag": "section",
"class": "lt-fbt-section",
"settings": [
{
"type": "text",
"id": "heading",
"label": "Heading",
"default": "Frequently bought together"
},
{
"type": "product_list",
"id": "products",
"label": "Products",
"limit": 4,
"info": "Hand-pick up to 4 products that go with this one."
},
{
"type": "checkbox",
"id": "include_current",
"label": "Include the product being viewed",
"default": true
},
{
"type": "text",
"id": "current_label",
"label": "Current product label",
"default": "This item"
},
{
"type": "text",
"id": "total_label",
"label": "Total label",
"default": "Total:"
},
{
"type": "text",
"id": "button_label",
"label": "Button label",
"default": "Add selected to cart"
},
{
"type": "text",
"id": "added_label",
"label": "Added label",
"default": "Added to cart"
},
{
"type": "select",
"id": "after_add",
"label": "After adding",
"options": [
{ "value": "cart", "label": "Go to the cart page" },
{ "value": "stay", "label": "Stay on the page" }
],
"default": "cart"
},
{
"type": "color",
"id": "button_color",
"label": "Button colour",
"default": "#111111"
},
{
"type": "color",
"id": "button_text",
"label": "Button text colour",
"default": "#ffffff"
},
{
"type": "color",
"id": "border_color",
"label": "Border colour",
"default": "#e5e5e5"
},
{
"type": "range",
"id": "padding",
"label": "Vertical padding",
"min": 0,
"max": 80,
"step": 4,
"unit": "px",
"default": 24
}
],
"presets": [
{
"name": "Bought together"
}
]
}
{% endschema %}
| Setting | Default |
|---|---|
| Heading | Frequently bought together |
| Products Hand-pick up to 4 products that go with this one. | — |
| Include the product being viewed | On |
| Current product label | This item |
| Total label | Total: |
| Button label | Add selected to cart |
| Added label | Added to cart |
| After adding | Go to the cart page |
| Button colour | #111111 |
| Button text colour | #ffffff |
| Border colour | #e5e5e5 |
| Vertical padding | 24px |
Further reading: How to upsell on Shopify
The section's products are set per template. Create one product template per group of products, add the section to each, and assign products to the right template.
This section adds the items at their normal price. Create an automatic discount in Shopify admin to reward the bundle, or use an app that discounts bundles for you.
Sold-out products are skipped automatically, so the block never offers something the shopper can't buy.
Hand-picking works for a few hero products. SellUp uses AI to match frequently bought together offers and add-ons to every product in your catalogue, so you don't have to set them up template by template.
Try SellUpA paid gift wrap add-on with an optional gift message, right above add to cart.
"You're £12 away from free shipping" with a bar that fills as the cart grows.
Buy 2 save 10%, buy 3 save 15%: a tiered pricing table with the price each worked out.
Discover more code snippets to enhance your Shopify store.
View All Sections