Recently Viewed Products
Shows each shopper the products they looked at, so they can get back to them in a tap.
A bar that slides up when the buy button scrolls off screen, with price and variant.
Important: Before making any changes to your theme, duplicate it in Online Store > Themes so you have a backup.
Long product pages push the buy button out of sight just as the shopper is convinced. The sticky bar slides up when the main add to cart button scrolls away, shows the selected variant and price, and presses your theme's own button so the cart behaves exactly as normal.
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-sticky-add-to-cart, delete the placeholder code, paste in the code below and click Save.
Click Customize, go to Products > Default product, click Add section and choose Sticky add to cart.
Change the settings in the sidebar, drag the section where you want it, and click Save.
{%- comment -%}
Sticky add-to-cart bar by LAUNCHTIP
https://launchtip.com/free-shopify-sections-code
Add it to the product template. The bar presses your theme's own
add-to-cart button, so the cart drawer, variant and quantity all behave
exactly as they do in the main product form.
{%- endcomment -%}
{%- if product != blank -%}
{%- assign current = product.selected_or_first_available_variant -%}
{%- style -%}
#lt-{{ section.id }} { position: fixed; left: 0; right: 0; bottom: 0; z-index: 50; background: {{ section.settings.background }}; color: {{ section.settings.text_color }}; border-top: 1px solid {{ section.settings.border_color }}; box-shadow: 0 -6px 20px rgba(0,0,0,0.08); transform: translateY(110%); transition: transform 0.25s ease; }
#lt-{{ section.id }}.is-visible { transform: translateY(0); }
#lt-{{ section.id }} .lt-sticky { display: flex; align-items: center; gap: 12px; max-width: 1200px; margin: 0 auto; padding: 10px 16px; }
#lt-{{ section.id }} img { width: 44px; height: 44px; object-fit: cover; border-radius: 6px; flex-shrink: 0; }
#lt-{{ section.id }} .lt-sticky__info { flex: 1; min-width: 0; }
#lt-{{ section.id }} .lt-sticky__title { margin: 0; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#lt-{{ section.id }} .lt-sticky__meta { margin: 0; font-size: 0.85em; opacity: 0.75; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#lt-{{ section.id }} .lt-sticky__button { background: {{ section.settings.button_color }}; color: {{ section.settings.button_text }}; border: 0; border-radius: 6px; padding: 12px 22px; font: inherit; font-weight: 600; cursor: pointer; white-space: nowrap; }
#lt-{{ section.id }} .lt-sticky__button[disabled] { opacity: 0.5; cursor: default; }
@media (max-width: 749px) {
#lt-{{ section.id }} img { display: none; }
{%- unless section.settings.show_on_mobile -%} #lt-{{ section.id }} { display: none; } {%- endunless -%}
}
{%- endstyle -%}
<div id="lt-{{ section.id }}" data-currency="{{ cart.currency.iso_code }}" data-action="{{ section.settings.click_action }}" aria-hidden="true">
<div class="lt-sticky">
{%- if product.featured_image != blank -%}
{{ product.featured_image | image_url: width: 120 | image_tag: loading: 'lazy', alt: product.title }}
{%- endif -%}
<div class="lt-sticky__info">
<p class="lt-sticky__title">{{ product.title }}</p>
<p class="lt-sticky__meta">
{%- unless product.has_only_default_variant -%}<span data-variant>{{ current.title }}</span> · {% endunless -%}
<span data-price>{{ current.price | money }}</span>
</p>
</div>
<button type="button" class="lt-sticky__button" tabindex="-1" {% unless current.available %}disabled{% endunless %}>
{%- if current.available -%}{{ section.settings.button_label }}{%- else -%}{{ section.settings.sold_out_label }}{%- endif -%}
</button>
</div>
<script type="application/json" data-variants>
{ {%- for v in product.variants -%}"{{ v.id }}": { "title": {{ v.title | json }}, "price": {{ v.price }}, "available": {{ v.available }} }{%- unless forloop.last -%},{%- endunless -%}{%- endfor -%} }
</script>
</div>
<script>
(function () {
var root = document.getElementById('lt-{{ section.id }}');
if (!root || root.dataset.ready) return;
root.dataset.ready = '1';
var form = document.querySelector('main form[action*="/cart/add"]') || document.querySelector('form[action*="/cart/add"]');
if (!form) return;
var mainButton = form.querySelector('[type="submit"], [name="add"]');
var button = root.querySelector('.lt-sticky__button');
var priceEl = root.querySelector('[data-price]');
var variantEl = root.querySelector('[data-variant]');
var variants = JSON.parse(root.querySelector('[data-variants]').textContent);
var addLabel = {{ section.settings.button_label | json }};
var soldOutLabel = {{ section.settings.sold_out_label | json }};
var lastId = null;
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 syncVariant() {
var input = form.querySelector('[name="id"]');
var id = input ? input.value : null;
if (!id || id === lastId || !variants[id]) return;
lastId = id;
var v = variants[id];
priceEl.textContent = money(v.price);
if (variantEl) variantEl.textContent = v.title;
button.disabled = !v.available;
button.textContent = v.available ? addLabel : soldOutLabel;
}
function show(visible) {
root.classList.toggle('is-visible', visible);
root.setAttribute('aria-hidden', visible ? 'false' : 'true');
button.tabIndex = visible ? 0 : -1;
}
var target = mainButton || form;
if ('IntersectionObserver' in window) {
new IntersectionObserver(function (entries) {
var entry = entries[0];
show(!entry.isIntersecting && entry.boundingClientRect.top < 0);
}).observe(target);
}
button.addEventListener('click', function () {
if (root.dataset.action === 'scroll' || !mainButton || mainButton.disabled) {
form.scrollIntoView({ behavior: 'smooth', block: 'center' });
return;
}
mainButton.click();
});
form.addEventListener('change', syncVariant);
setInterval(syncVariant, 500);
syncVariant();
})();
</script>
{%- endif -%}
{% schema %}
{
"name": "Sticky add to cart",
"tag": "section",
"class": "lt-sticky-section",
"enabled_on": {
"templates": ["product"]
},
"settings": [
{
"type": "text",
"id": "button_label",
"label": "Button label",
"default": "Add to cart"
},
{
"type": "text",
"id": "sold_out_label",
"label": "Sold out label",
"default": "Sold out"
},
{
"type": "select",
"id": "click_action",
"label": "When the button is pressed",
"options": [
{ "value": "add", "label": "Add to cart" },
{ "value": "scroll", "label": "Scroll up to the product options" }
],
"default": "add",
"info": "Choose scroll if shoppers need to pick a size or colour first."
},
{
"type": "checkbox",
"id": "show_on_mobile",
"label": "Show on mobile",
"default": true
},
{
"type": "color",
"id": "background",
"label": "Background",
"default": "#ffffff"
},
{
"type": "color",
"id": "text_color",
"label": "Text colour",
"default": "#111111"
},
{
"type": "color",
"id": "border_color",
"label": "Border colour",
"default": "#e5e5e5"
},
{
"type": "color",
"id": "button_color",
"label": "Button colour",
"default": "#111111"
},
{
"type": "color",
"id": "button_text",
"label": "Button text colour",
"default": "#ffffff"
}
],
"presets": [
{
"name": "Sticky add to cart"
}
]
}
{% endschema %}
| Setting | Default |
|---|---|
| Button label | Add to cart |
| Sold out label | Sold out |
| When the button is pressed Choose scroll if shoppers need to pick a size or colour first. | Add to cart |
| Show on mobile | On |
| Background | #ffffff |
| Text colour | #111111 |
| Border colour | #e5e5e5 |
| Button colour | #111111 |
| Button text colour | #ffffff |
Yes. It presses the theme's own add to cart button, so it uses whatever is selected in the main form.
It sits at the bottom of the screen. If something else lives there, raise or lower the z-index in the section's CSS.
It works with any theme whose product form posts to /cart/add, which is nearly all of them. Check it on your theme before publishing.
Shows each shopper the products they looked at, so they can get back to them in a tap.
Text and images that change when the shopper picks a different colour or size.
Your latest blog posts in a clean grid with images, dates and excerpts.
Discover more code snippets to enhance your Shopify store.
View All Sections