jelonyx
No-app fixes · Fix 01

Shopify sticky add to cart
without an app.

The default Shopify product page buries the add to cart button the moment a customer scrolls. Most solutions require a paid app. This fix solves it with a single Liquid snippet, no monthly fee, and no script injected by a third party.

DifficultyBeginner
Time~15 minutes
CostFree
Theme accessRequired
Compatible with:Dawn 12+HorizonSenseCraftRefreshMost OS 2.0 themes
Preview workbench

Preview the sticky cart bar.

Change the product copy, price, button label, trust line, and button colour. The live snippet mirrors your theme's real product form after the main button scrolls away.

Live storefront preview
Product page

Everyday Linen Shirt

$89

Everyday Linen Shirt$89 / Free shipping over $100

The code

One file. Copy all of it.

snippets/jlx-sticky-atc.liquid
Liquid · CSS · JS
{% comment %}
  Jelonyx · Sticky Add to Cart : no app required
  Compatible with Dawn 12+, Horizon, and most Online Store 2.0 themes
  Source: jelonyx.com/shopify/no-app/sticky-add-to-cart
{% endcomment %}

{%- if product != blank -%}

<div
  id="jlx-sticky-atc"
  class="jlx-sticky-atc"
  aria-hidden="true"
  role="region"
  aria-label="Quick add to cart"
>
  <div class="jlx-sticky-atc__inner">

    <div class="jlx-sticky-atc__product">
      {%- if product.featured_image != blank -%}
        {{
          product.featured_image
          | image_url: width: 96
          | image_tag:
              width: 48,
              height: 48,
              class: 'jlx-sticky-atc__img',
              alt: product.featured_image.alt,
              loading: 'lazy'
        }}
      {%- endif -%}
      <span class="jlx-sticky-atc__title">{{ product.title }}</span>
    </div>

    <button
      type="button"
      class="jlx-sticky-atc__btn"
      disabled
    >
      {{ 'products.product.add_to_cart' | t }}
    </button>

  </div>
</div>

<style>
  .jlx-sticky-atc {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 200;
    background: var(--color-background, #ffffff);
    border-top: 1px solid var(--color-border, rgba(0, 0, 0, 0.1));
    box-shadow: 0 -2px 16px rgba(0, 0, 0, 0.06);
    padding: 10px 20px;
    transform: translateY(110%);
    transition: transform 0.25s ease;
  }
  .jlx-sticky-atc.jlx-is-visible {
    transform: translateY(0);
  }
  .jlx-sticky-atc__inner {
    max-width: 1200px;
    margin: 0 auto;
    display: flex;
    align-items: center;
    gap: 16px;
  }
  .jlx-sticky-atc__product {
    display: flex;
    align-items: center;
    gap: 12px;
    flex: 1;
    min-width: 0;
  }
  .jlx-sticky-atc__img {
    width: 48px;
    height: 48px;
    object-fit: cover;
    border-radius: 4px;
    flex-shrink: 0;
  }
  .jlx-sticky-atc__title {
    font-size: 14px;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    color: var(--color-foreground, #1a1a1a);
  }
  .jlx-sticky-atc__btn {
    background: var(--color-button, #1a1a1a);
    color: var(--color-button-text, #ffffff);
    border: 1px solid transparent;
    border-radius: 4px;
    padding: 10px 24px;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    flex-shrink: 0;
    transition: opacity 0.15s ease;
    font-family: inherit;
  }
  .jlx-sticky-atc__btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
  }
  @media (max-width: 480px) {
    .jlx-sticky-atc__img,
    .jlx-sticky-atc__title { display: none; }
    .jlx-sticky-atc__btn { flex: 1; }
  }
</style>

<script>
  (function () {
    'use strict';

    var bar = document.getElementById('jlx-sticky-atc');
    if (!bar) return;

    var form = document.querySelector('form[action*="/cart/add"]');
    var mainBtn =
      (form && form.querySelector('[name="add"]')) ||
      document.querySelector('[name="add"]') ||
      document.querySelector('.product-form__submit');

    if (!mainBtn) return;

    var stickyBtn = bar.querySelector('.jlx-sticky-atc__btn');

    // Show/hide bar when the main button leaves the viewport
    var observer = new IntersectionObserver(
      function (entries) {
        var offScreen = !entries[0].isIntersecting;
        bar.classList.toggle('jlx-is-visible', offScreen);
        bar.setAttribute('aria-hidden', offScreen ? 'false' : 'true');
      },
      { threshold: 0 }
    );
    observer.observe(mainBtn);

    // Mirror button state (sold out, loading, etc.)
    function syncState() {
      stickyBtn.disabled = mainBtn.disabled;
      var label = mainBtn.textContent.trim();
      if (label) stickyBtn.textContent = label;
    }
    syncState();
    new MutationObserver(syncState).observe(mainBtn, {
      attributes: true,
      childList: true,
      subtree: true,
    });

    // Trigger the main form on click
    stickyBtn.addEventListener('click', function () {
      if (stickyBtn.disabled) return;
      if (form && typeof form.requestSubmit === 'function') {
        form.requestSubmit();
      } else {
        mainBtn.click();
      }
    });
  })();
</script>

{%- endif -%}

How to install

  1. 01
    Open your theme code editor.

    In Shopify Admin, go to Online Store → Themes. On your active theme, click the three-dot menu and select Edit code.

  2. 02
    Create a new snippet.

    Under the Snippets folder, click Add a new snippet. Name it jlx-sticky-atc and click Done.

  3. 03
    Paste the full snippet code.

    Delete any placeholder content and paste the entire code block above. Save the file.

  4. 04
    Add the render tag to your product template.

    Open sections/main-product.liquid. Scroll to the bottom of the file and find the line starting with {% schema %}. Add the following on the line directly above it, then save.

    {% render 'jlx-sticky-atc' %}
  5. 05
    Test on a product page.

    Open any product page. Scroll down past the add to cart button. The sticky bar should slide up from the bottom of the screen. On mobile, it fills the full width.

How it works

The snippet uses the browser's IntersectionObserver API to track when your theme's main add to cart button leaves the viewport. When the button scrolls out of view, the sticky bar slides into view. When the button comes back, the bar hides.

Clicking the sticky button triggers the main product form directly. It respects your theme's cart behaviour (cart drawer, cart page redirect, or mini cart) without any extra configuration.

Button state is mirrored via a MutationObserver, so if a variant sells out or the button text changes during loading, the sticky bar reflects that automatically.

Compatibility

Tested against Dawn 12+ and Horizon. Both themes use standard CSS variables (--color-background, --color-button, --color-button-text, --color-foreground) that the snippet reads automatically, so the bar matches your store colours without any CSS changes.

On themes that do not define these variables, the snippet falls back to neutral values (white background, dark button). You can override the .jlx-sticky-atc and .jlx-sticky-atc__btn classes in the snippet CSS to hard-code your colours if needed.

The snippet uses IntersectionObserver and MutationObserver, both of which are supported in all browsers used by Shopify storefront visitors.

Limitations

  • Variant pickers: the snippet does not include a variant selector in the sticky bar. Customers must select a variant on the product page before using the sticky button. For a sticky bar with variant selection, that is a more involved build.
  • Custom ATC apps: if you use an app that replaces your theme's standard product form (e.g. a custom bundle builder), the snippet may not connect to it correctly. Check whether the form still has action*="/cart/add" or a button with name="add".
  • Headless storefronts: this is a Liquid snippet and does not apply to Hydrogen or other headless setups.
  • Custom theme positioning: if your theme has a persistent sticky element at the bottom (chat widget, cookie bar), you may need to adjust the snippet's z-index or add a bottom offset.
No-App Shopify Fix Sprint

Need this installed?

If you would rather not touch theme code, we can install, test, and style this for your store. We will also handle any compatibility issues with your specific theme or apps.