Key takeaways
- A sticky add to cart bar keeps the product title, price, variant selector, and Add to cart button in view as the shopper scrolls a long product page - and sits as a bottom bar on mobile.
- It reduces friction on long PDPs. The buy button is always one tap away, so shoppers never scroll back up to purchase.
- The native build is a CSS
position:stickyor fixed bar, an IntersectionObserver that shows it once the main Add to cart scrolls out of view, and a small script that keeps the variant and price in sync.- Done right it costs almost no page weight. Done through a heavy app, it can add JavaScript and layout shift you didn’t need.
- Fudge builds the bar as native theme code - no widget, on-brand, and it stays when you uninstall.
A sticky add to cart bar is one of the highest-impact changes you can make to a Shopify product page. On a long PDP with reviews, specs, and lifestyle imagery, the main buy button scrolls out of view fast. A sticky bar keeps it reachable the whole way down.
This guide covers what the pattern is, the CRO case for it, the two main variants, how to build it natively in a theme, and the accessibility and performance details that separate a good bar from an annoying one.
Why you can trust us
Jacques has 15+ years of development experience and has built product page patterns like this across hundreds of Shopify stores. We build Fudge, an AI storefront editor with a 5.0 rating on the Shopify App Store, so we work on the same theme layer a sticky bar lives on and see what breaks when it’s done badly.
What is a sticky add to cart bar?
A sticky add to cart bar is a compact strip that stays fixed to the edge of the viewport while the rest of the page scrolls. It repeats the essentials from the main buy box: product title, current price, variant selector, quantity, and the Add to cart button.
On desktop it usually sits at the top of the screen. On mobile it sits at the bottom, within thumb reach.
The point is simple. The main Add to cart button is only visible near the top of a PDP. Everything below it - the description, size guide, reviews, shipping info, related products - pushes the buy button off screen. Without a sticky bar, a shopper who reads to the bottom and decides to buy has to scroll all the way back up. That scroll is friction, and friction costs conversions.
The CRO case
The rationale is about removing steps at the moment of intent. A shopper forms the decision to buy while reading detail lower on the page. A sticky bar lets them act on that decision immediately instead of hunting for the button.
The effect is largest where PDPs are long and where most traffic is mobile, which describes most Shopify stores. On a small screen the main buy button leaves the viewport after a single swipe, so a persistent bottom bar carries real weight.
A sticky bar is one tactic inside a broader product page strategy. For the wider picture, see our Shopify CRO guide, and for the layout mechanics around it, how to customise a Shopify product page.
The two patterns: top bar vs mobile bottom bar
There are two common shapes, and most stores ship both from the same code.
1. Desktop top sticky bar. A slim bar pinned to the top of the window. It typically shows a product thumbnail, title, price, a compact variant dropdown, and the Add to cart button aligned right. It appears once the main buy box scrolls past.
2. Mobile bottom bar. A full-width bar pinned to the bottom of the screen, above the thumb. On mobile, screen space is tight and the reach zone is the bottom, so a bottom bar converts better than a top one. It often collapses the variant picker into a compact select or a tap-to-expand sheet.
The content is the same; only placement and density change by breakpoint. Keep the bar short so it never covers more than a thin strip of the page.
How to build a sticky add to cart natively in your theme
You do not need an app for this. A native build has three parts: the sticky element, the show/hide trigger, and the variant sync.
1. The sticky element
For a bar that follows the shopper down the whole page, use a fixed or sticky-positioned element. CSS position:sticky keeps an element pinned relative to its scroll container once it crosses a threshold you set with top or bottom, and it does this on the browser’s compositor with no JavaScript.1 For a bar that must escape the product section and follow the entire page, position:fixed on a top-level element is the simpler choice, since sticky is constrained by its parent and can be broken by an ancestor with overflow:hidden.
Markup is a single container with the buy essentials, hidden by default:
<div class="sticky-atc" data-sticky-atc hidden>
<span class="sticky-atc__title">Product name</span>
<span class="sticky-atc__price" data-sticky-price>£49.00</span>
<!-- compact variant select mirrors the main picker -->
<button type="submit" form="product-form-id">Add to cart</button>
</div>
Note the form attribute. A button can submit a form it isn’t nested inside by referencing that form’s id, so the bar can reuse the theme’s existing product form instead of duplicating it.
2. Show and hide it with IntersectionObserver
The bar should only appear once the real Add to cart button has scrolled out of view. The efficient way to detect that is IntersectionObserver, which watches a target element and fires a callback when it enters or leaves the viewport. Unlike a scroll listener, it runs asynchronously off the main thread and only fires when the threshold is actually crossed, so it doesn’t fire on every scroll frame.2
Point it at the main buy button and toggle the bar:
const mainButton = document.querySelector('[data-main-atc]');
const bar = document.querySelector('[data-sticky-atc]');
const observer = new IntersectionObserver(([entry]) => {
bar.hidden = entry.isIntersecting; // hide bar while main button is on screen
}, { rootMargin: '0px' });
observer.observe(mainButton);
When the main button is visible, hide the bar. When it scrolls away, show it. That is the entire mechanic.
3. Keep the variant and price in sync
The bar is a second view of the same product, so it has to reflect the currently selected variant. The rule is one source of truth: the main product form.
When the shopper changes a variant in the main picker, most Online Store 2.0 themes (Dawn and its descendants) dispatch a change event you can listen to. On that event, update the bar’s displayed price and its hidden variant ID input so the two views never disagree. If the bar has its own compact select, wire it to write back to the main picker rather than tracking state on its own.
4. Submit to the cart
You have two ways to add to cart from the bar.
- Reuse the product form. Point the bar’s button at the theme’s product form with the
formattribute above. The theme’s existing add-to-cart handling takes over, including its cart drawer. - POST to the AJAX cart. Send the selected variant ID and quantity to
/cart/add.js. It’s a POST that takes anitemsarray (each withidandquantity), returns the added line items as JSON, and returns a 422 error if the variant is sold out or exceeds available stock.3 After a success, refresh the cart count and open the drawer.
Reusing the product form is the more reliable default, because it inherits whatever the theme already does on add - drawer, toasts, upsells, and analytics events.
Accessibility: keep it reachable, don’t trap it
A sticky bar overlays content, so it has real accessibility responsibilities.
- Don’t cover content. Reserve space so the bar never sits on top of the footer, cookie notice, or the last line of text. On mobile, add bottom padding to the page equal to the bar’s height.
- Keep it in the tab order and don’t trap focus. The variant select and button must be keyboard reachable, and focus must move freely in and out. A sticky bar is not a modal, so never trap focus inside it.
- Meet contrast requirements. Text and the button need sufficient colour contrast against the bar background, the same standard as the rest of the page.
- Announce the price. When the variant changes and the price updates, the visible price text is enough for most cases; avoid firing noisy live-region announcements on every keystroke.
- Respect reduced motion. If the bar slides in, wrap the transition so it’s disabled for users who prefer reduced motion.
Performance: cheap if you build it right
The native version is close to free. position:sticky and position:fixed are handled by the browser’s compositor, and IntersectionObserver replaces the expensive scroll listeners older sticky scripts relied on. The whole feature is a few lines of CSS and a small script.
The cost comes from apps. A sticky-bar app ships its own JavaScript to every product page, and the heavier ones add a payload plus a layout-shift hit as the bar injects late. On a store already carrying a stack of apps, that compounds, and page speed maps directly to conversion.
If your PDP is already slow, fix that first. See how to speed up a Shopify theme before adding anything new to the page.
When not to use a sticky add to cart
It isn’t universal. Skip or rethink it when:
- Your PDP is short. If the main buy button is visible for most of the page, a bar adds clutter without solving a problem.
- Screen space is precious. On very small viewports a tall bar that eats content is worse than no bar. Keep it thin, or don’t ship it.
- The product needs configuration before purchase. For made-to-order or heavily customised products where the shopper must make choices first, a one-tap bar can push people to buy before they’ve configured, causing errors or returns.
- It fights another fixed element. Chat widgets, cookie banners, and promo bars can stack into a wall of fixed UI. Audit what’s already pinned before adding more.
App vs native: the tradeoff
You can add a sticky bar with an app in a few clicks, and for a non-technical store that’s a reasonable start. The tradeoffs are the usual ones for widget apps: a monthly fee, added JavaScript, a bar that may not match your theme, and a dependency - uninstall the app and the bar disappears.
Native code avoids all four. The cost is that someone has to write it, and get the variant sync and accessibility right. That’s the gap the next section fills.
Where Fudge fits
Fudge is an AI storefront editor that writes native theme code. You describe the bar - “add a sticky add to cart on my product pages that appears once the main buy button scrolls off, with a bottom bar on mobile that keeps the variant and price in sync” - and it writes the Liquid, CSS, and JavaScript directly into your theme.
Because the output is native, there is no third-party widget rendering over your store, no extra script loading on every PDP from an app, and no monthly bar fee. It reads like your storefront, not like a bolted-on form.
And because it’s theme code, it survives uninstall. Cancel Fudge and the sticky bar stays exactly where it is, the opposite of an app-rendered element that vanishes when you remove the app. The same holds for anything else you build with the Shopify store editor or the Fudge page builder.
If you want a quick-buy pattern on collection pages too, the related move is a Shopify quick view modal, which lets shoppers add to cart without opening the full PDP.
FAQ
It helps most on long product pages and on mobile, where the main buy button leaves the screen quickly. Keeping the price, variant, and Add to cart button reachable removes the scroll-back-up friction at the moment a shopper decides to buy. It does little on short PDPs where the button is already in view.
No. A sticky bar is a small amount of native theme code: a fixed or position:sticky element, an IntersectionObserver to show it once the main button scrolls off, and a script to keep the variant and price in sync. Apps are quicker to install but add a monthly fee, extra JavaScript, and a dependency that removes the bar on uninstall.
Use an IntersectionObserver pointed at the main Add to cart button. When the button is intersecting the viewport, hide the bar; when it scrolls out of view, show it. IntersectionObserver runs off the main thread and only fires when the threshold is crossed, so it is far cheaper than a scroll event listener.
Treat the main product form as the single source of truth. Listen for variant changes on the main picker, update the bar's price and hidden variant ID to match, then either submit the main product form via the button's form attribute or POST the variant ID and quantity to /cart/add.js.
The native version barely does. CSS position:sticky and fixed run on the compositor, and IntersectionObserver replaces heavy scroll handlers. The weight comes from apps that ship their own JavaScript to every product page. If speed matters, build it in the theme rather than through a widget app.
Only if built carelessly. Keep the bar thin so it doesn't cover content, reserve page padding for it, ensure the variant select and button are keyboard reachable, never trap focus inside it, and meet colour contrast requirements. Done right, it is an accessible convenience rather than an obstacle.
Footnotes
-
MDN Web Docs, “position” - a
position: stickyelement is treated as relatively positioned until its containing block crosses a threshold set bytop/bottom, then sticks. It is composited by the browser without JavaScript. https://developer.mozilla.org/en-US/docs/Web/CSS/position ↩ -
MDN Web Docs, “Intersection Observer API” - asynchronously observes when a target element enters or leaves the viewport and fires a callback only when a threshold is crossed, avoiding the cost of per-frame scroll listeners. https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver ↩
-
Shopify Dev, “Cart AJAX API” -
POST /cart/add.jsadds variants to the cart via anitemsarray (each withidandquantity), returns the added line items as JSON, and returns a 422 error when quantity exceeds available stock. https://shopify.dev/docs/api/ajax/reference/cart ↩