Sticky Add-to-Cart Bar

A bar that slides up when the buy button scrolls off screen, with price and variant.

One section file Product page Edit in the theme editor

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

What you'll build

Trail Running Shoe UK 9 / Blue · £89.00 Add to cart

Preview with the default settings. Colours, text and layout can all be changed in the theme editor.

Install

Add it to your store in 4 steps

  1. 1

    Open your theme code

    In Shopify admin go to Online Store > Themes. Next to the theme you want to edit, click the three dots and choose Edit code.

  2. 2

    Create the section

    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.

  3. 3

    Add it in the theme editor

    Click Customize, go to Products > Default product, click Add section and choose Sticky add to cart.

  4. 4

    Make it yours

    Change the settings in the sidebar, drag the section where you want it, and click Save.

sections/lt-sticky-add-to-cart.liquid · 185 lines
{%- 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> &middot; {% 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 %}
Customization

What you can change in the theme editor

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
Good to know

Tips for setting it up

  • Set When the button is pressed to Scroll up to the product options if most of your products need a size or colour choice.
  • The bar follows the variant picked in the main form, including price and sold out state.
  • Turn off Show on mobile if your theme already has a sticky buy button on small screens.
FAQ

Frequently asked questions

Does it add the right variant and quantity?

Yes. It presses the theme's own add to cart button, so it uses whatever is selected in the main form.

Will it cover my cookie banner or chat button?

It sits at the bottom of the screen. If something else lives there, raise or lower the z-index in the section's CSS.

Does it work on every theme?

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.

More free sections

New

Recently Viewed Products

Shows each shopper the products they looked at, so they can get back to them in a tap.

New

Variant-Aware Content

Text and images that change when the shopper picks a different colour or size.

New

Blog Posts Grid for Homepage

Your latest blog posts in a clean grid with images, dates and excerpts.

Explore More Free Sections

Discover more code snippets to enhance your Shopify store.

View All Sections
SellUp Increase AOV with Upsells