How to Add a Sticky Add to Cart Bar on Shopify Product Pages

2026-10-13 · upselling

A sticky add to cart bar keeps the buy button visible after a shopper scrolls past your product form. On long product pages — the ones with size charts, ingredient lists, reviews, and FAQ blocks — that's often the difference between a sale and a bounce. This guide covers the no-app method using your theme's Liquid files, plus a no-code fallback if you'd rather not touch code.

What a Sticky Add to Cart Bar Does (and When It's Worth Adding)

A sticky add to cart bar is a fixed element that appears at the bottom (or top) of the viewport once the shopper scrolls past the original Add to Cart button. It typically contains the product title, price, variant selector or a link back to the variant picker, and a button that submits the same form as the main product page.

It's worth adding when:

Skip it if your product pages are short, if you sell one-off or made-to-order items where a deliberate decision process matters, or if your theme already includes a sticky bar (Dawn, Craft, and several paid themes ship with one built in — check your theme settings under Product page before writing any code).

Before You Start: Theme Backups and Duplicating Your Product Template

Never edit a live theme's Liquid directly. Do this instead:

  1. Duplicate your theme. In Shopify admin, go to Online Store → Themes, click the three dots next to your live theme, and choose Duplicate. Work on the copy. Only publish it once you've tested everything.
  2. Duplicate the product template. Go to Online Store → Themes → Edit code. Under Templates, find product.json (or product.liquid on older themes). Click Duplicate and rename the copy, e.g. product.sticky.json.
  3. Assign the template to one product first. Open a single test product, scroll to Theme template in the right sidebar, and select your new template. This lets you test on one URL without affecting your whole catalog.
  4. Note your theme's naming conventions. In JSON templates, sections are referenced by filename. In Liquid templates, snippets are included with {% render 'filename' %}. Match whatever your theme already does.

If your theme uses a section-based product page (most modern themes do), you'll add a new section file rather than editing the product template directly. That's cleaner and easier to remove later.

How to Add a Sticky Add to Cart Bar on Shopify Product Pages Without an App

The approach: create a new section that renders a hidden sticky bar, then use JavaScript to reveal it when the main product form scrolls out of view. The bar submits the existing product form, so variant selection, quantity, and line item properties all stay in sync automatically.

Steps:

  1. In Edit code, open the Sections folder and click Add a new section. Name it sticky-add-to-cart.
  2. Paste the code from the next section into the new file.
  3. Open your product template (product.json or product.liquid) and add the section to the template's section list, or render it with {% section 'sticky-add-to-cart' %} if your theme uses Liquid templates.
  4. Save, then preview the assigned test product.

The critical detail is form attribute on the button. HTML lets a button submit a form it isn't nested inside, as long as the button's form attribute matches the form's id. Shopify's product form has a predictable ID in most themes (product-form-{{ section.id }} in Dawn-based themes, AddToCartForm in many older ones). Check your theme's product-form.liquid snippet or main product section to confirm the exact ID, and update the snippet below to match.

The Code Snippet: HTML, CSS, and JavaScript for Your Sticky Bar

{%- assign current_variant = product.selected_or_first_available_variant -%}

<div
  id="sticky-atc-{{ section.id }}"
  class="sticky-atc"
  data-sticky-atc
  data-form-id="product-form-{{ section.id }}"
  aria-hidden="true"
>
  <div class="sticky-atc__inner">
    <div class="sticky-atc__info">
      <span class="sticky-atc__title">{{ product.title | escape }}</span>
      <span class="sticky-atc__price">{{ current_variant.price | money }}</span>
    </div>
    <button
      type="submit"
      form="product-form-{{ section.id }}"
      name="add"
      class="sticky-atc__button"
      {% unless current_variant.available %}disabled{% endunless %}
    >
      {% if current_variant.available %}Add to cart{% else %}Sold out{% endif %}
    </button>
  </div>
</div>

{% style %}
  .sticky-atc {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 20;
    background: #fff;
    border-top: 1px solid rgba(0,0,0,0.1);
    transform: translateY(100%);
    transition: transform 0.25s ease;
    padding: 10px 16px;
    padding-bottom: calc(10px + env(safe-area-inset-bottom));
  }
  .sticky-atc.is-visible {
    transform: translateY(0);
  }
  .sticky-atc__inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    max-width: 1200px;
    margin: 0 auto;
  }
  .sticky-atc__title {
    display: block;
    font-size: 14px;
    font-weight: 600;
  }
  .sticky-atc__price {
    font-size: 13px;
    opacity: 0.75;
  }
  .sticky-atc__button {
    flex: 0 0 auto;
    padding: 12px 20px;
    border: 0;
    border-radius: 6px;
    background: #111;
    color: #fff;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
  }
  .sticky-atc__button[disabled] {
    opacity: 0.5;
    cursor: not-allowed;
  }
  @media (max-width: 749px) {
    .sticky-atc__title { font-size: 13px; }
  }
{% endstyle %}

<script>
  (function () {
    var bar = document.querySelector('[data-sticky-atc]');
    if (!bar) return;

    var formId = bar.getAttribute('data-form-id');
    var form = document.getElementById(formId);
    if (!form) return;

    var trigger = form.querySelector('[type="submit"]');
    if (!trigger) return;

    var observer = new IntersectionObserver(
      function (entries) {
        entries.forEach(function (entry) {
          var past = !entry.isIntersecting && entry.boundingClientRect.top < 0;
          bar.classList.toggle('is-visible', past);
          bar.setAttribute('aria-hidden', past ? 'false' : 'true');
        });
      },
      { threshold: 0 }
    );

    observer.observe(trigger);
  })();
</script>

Two things to verify before publishing: the data-form-id value matches your theme's actual form ID, and the price updates when a shopper changes variants. On most themes the price in the bar will be stale after a variant change unless you hook into the theme's variant-change event. If your theme fires one (Dawn-based themes publish a variant:change event on the document), listen for it and update .sticky-atc__price with the new variant's price.

Testing Your Sticky Bar on Mobile and Desktop

Run through this checklist on a duplicated theme before publishing:

Test on at least one iOS device, one Android device, and desktop Chrome and Safari. Browser dev tools' device emulation misses safe-area and scroll momentum behavior.

No-Code Alternative: A Cheaper App If You'd Rather Not Touch Liquid

If editing Liquid feels risky or you don't want to maintain the snippet when your theme updates, a dedicated app is the pragmatic choice. Several Shopify apps in the conversion-optimization category offer sticky add to cart bars as a core feature, including Releasit, Essential Add to Cart, and Sticky Add to Cart by various developers. Most install in a few clicks, let you style the bar from the theme editor, and handle variant syncing automatically.

What to check before installing:

For a single-store merchant, the app route usually costs less time than maintaining a custom snippet — but the snippet gives you full control over styling and zero monthly cost.

Common Issues and Quick Fixes

Symptom Likely cause Fix
Bar never appears Form ID in data-form-id doesn't match your theme Inspect the product form in dev tools and copy the exact id
Button adds wrong variant Sticky button submits a stale form or a separate form Ensure the button's form attribute points to the live product form
Bar covers footer content Fixed positioning with no body padding Add padding-bottom to body equal to the bar height when visible
Price doesn't update on variant change No listener for the theme's variant-change event Subscribe to variant:change and update the price node
Bar appears immediately on load IntersectionObserver targeting the wrong element Observe the main submit button, not the form container
Overlaps chat widget Competing z-index values Lower the bar's z-index or raise the widget's bottom offset

Conclusion

A sticky add to cart bar is one of the highest-leverage changes you can make to a long product page, and the no-app version is a single section file plus about 60 lines of CSS and JavaScript. Duplicate your theme, test on a single product template, and verify variant syncing before you publish. If maintaining Liquid isn't your thing, a reputable app gets you the same result in minutes — just check current pricing and theme compatibility first.

FAQ

Will a sticky bar hurt my page speed? The snippet above adds roughly 2KB of CSS and JavaScript, which is negligible. Apps vary — check your Lighthouse score before and after installing, and prefer apps that load via theme app extensions rather than injecting code into your theme files.

Does this work with Shopify's new themes like Dawn? Yes. Dawn and themes built on the same architecture use predictable form IDs and fire a variant:change event, which makes the snippet easier to wire up. You'll need to confirm the form ID matches what your theme generates.

Can I show the bar only on mobile? Yes. Wrap the .sticky-atc styles in a @media (max-width: 749px) block, or add a display condition in the section settings. Many merchants show it on all devices since desktop shoppers scroll long pages too — test both and compare conversion rates in your Shopify analytics.