To add a sticky Add to Cart button on Shopify without an app, create a snippet containing a fixed bottom bar with the product image, title, price, and a button linked to the existing product form via the HTML form attribute. Use an IntersectionObserver to show the bar only after the main Add to Cart button scrolls out of view.
Here’s a scene that plays out on your product pages every single day.
A shopper lands on your product. They scroll down β past the price, past the Add to Cart button β reading your description, checking the reviews, studying the photos. They’re convinced. They want it. And nowβ¦ the buy button is somewhere far above them, off-screen. They have to scroll all the way back up to purchase.
On desktop that’s a minor annoyance. On mobile β where most of your traffic lives and product pages run long β it’s a genuine conversion leak. Every extra scroll between “I want this” and “Add to Cart” gives hesitation a chance to win. Some shoppers scroll back. Some get distracted. Some just leave.
The fix is a pattern you’ve seen on every major store: a sticky Add to Cart bar β a slim bar pinned to the bottom of the screen showing the product, its price, and a buy button that’s always one tap away, no matter how far the shopper has scrolled.
Shopify has no built-in setting for this, and Dawn doesn’t ship one. The App Store will happily rent you a sticky bar for $5β$10 a month. But the honest truth from someone who builds these for client stores: a sticky Add to Cart is a small, self-contained piece of code β a snippet, some CSS, and a few lines of JavaScript. In this tutorial I’ll give you all of it, free, including the two details that separate a professional implementation from a clumsy one:
- Smart visibility β the bar appears only after the real Add to Cart button scrolls out of view (a bar that’s always there is redundant and annoying)
- True cart integration β the sticky button submits your theme’s existing product form, so Dawn’s cart drawer, quantity logic, and variant selection all keep working exactly as designed
No app, no monthly fee, roughly 25 minutes. Let’s build it.
Table of Contents
- Why a Sticky Add to Cart Button Increases Conversions
- How This Implementation Works (The Two Clever Tricks)
- Before You Start
- Step 1: Create the Sticky Add to Cart Snippet
- Step 2: Add the CSS
- Step 3: Render the Snippet on the Product Page
- Step 4: Test the Behavior
- Customizations
- Mobile-Only Sticky Bar
- Live Price Sync on Variant Change
- Styling Tweaks
- Best Practices for Sticky Add to Cart Bars
- Common Mistakes to Avoid
- Troubleshooting
- FAQ
- Conclusion
Why a Sticky Add to Cart Button Increases Conversions
The logic is simple friction math:
- Product pages are long β especially on mobile. Descriptions, size charts, reviews, FAQ accordions. The further a shopper scrolls, the further the buy button recedes.
- Purchase intent peaks mid-page. Shoppers decide while reading the description or reviews β exactly when the button isn’t visible. A sticky bar puts the action next to the moment of decision.
- Mobile thumbs love bottom bars. The bottom of the screen is the easiest tap zone on a phone. A bottom-pinned buy button is ergonomically where the purchase wants to happen.
- It’s a familiar pattern. Amazon, fashion giants, and virtually every high-converting DTC store use persistent buy actions. Shoppers already know how to use it β there’s zero learning curve.
Like every conversion pattern, it amplifies a good product page rather than rescuing a bad one. But of all the small customizations I install for clients, sticky ATC is consistently one of the highest impact-per-hour.
How This Implementation Works (The Two Clever Tricks)
Before pasting code, understand the two decisions that make this version solid:
Trick 1 β the HTML form attribute. The naive approach builds a second, separate add-to-cart form in the sticky bar. That path leads to pain: duplicated variant logic, a full page reload to the cart instead of Dawn’s cart drawer, and quantity mismatches. Instead, our sticky button uses the HTML form="" attribute to submit Dawn’s existing product form β the same one the main button uses. One form, two buttons. The cart drawer opens, the selected variant is respected, quantity is respected, and any theme cart logic keeps working. It’s one attribute doing the work of fifty lines of JavaScript.
Trick 2 β IntersectionObserver. The bar shouldn’t exist while the real buy button is on screen. We watch the main button with an IntersectionObserver (a tiny, efficient browser API β no scroll-event spaghetti), and slide the bar in only when the button leaves the viewport, sliding it back out when the shopper returns to the top.
Everything else is presentation: product image, title, price, button.
Before You Start
You’ll need a store on Dawn or a Dawn-based theme (Sense, Craft, Refresh, Studio β near-identical code; other OS 2.0 themes work with the same concepts and different form IDs), and about 25 minutes.
And the permanent rule: duplicate your theme. Go to Online Store β Themes β β― β Duplicate, edit the copy, publish when it works.
Step 1: Create the Sticky Add to Cart Snippet
- Go to Online Store β Themes β β― β Edit code on your duplicate
- In the Snippets folder, click Add a new snippet, name it
sticky-atc, and paste:
liquid
{%- comment -%}
Sticky Add to Cart bar
Submits the theme's existing product form via the HTML form attribute.
{%- endcomment -%}
{%- assign current_variant = product.selected_or_first_available_variant -%}
{%- assign product_form_id = 'product-form-' | append: section.id -%}
<div class="sticky-atc" id="StickyATC-{{ section.id }}" aria-hidden="true">
<div class="sticky-atc__inner page-width">
{%- if product.featured_media -%}
<img
class="sticky-atc__image"
src="{{ product.featured_media | image_url: width: 120 }}"
alt="{{ product.featured_media.alt | default: product.title | escape }}"
width="60"
height="{{ 60 | divided_by: product.featured_media.aspect_ratio | round }}"
loading="lazy"
>
{%- endif -%}
<div class="sticky-atc__details">
<span class="sticky-atc__title">{{ product.title | escape }}</span>
<span class="sticky-atc__price" id="StickyATCPrice-{{ section.id }}">
{{ current_variant.price | money }}
</span>
</div>
<button
type="submit"
name="add"
form="{{ product_form_id }}"
class="sticky-atc__button button button--full-width"
{% if current_variant.available == false %}disabled{% endif %}
>
{%- if current_variant.available -%}
Add to Cart
{%- else -%}
Sold Out
{%- endif -%}
</button>
</div>
</div>
<script>
(function () {
var bar = document.getElementById('StickyATC-{{ section.id }}');
if (!bar) return;
/* Watch the theme's main buy button area */
var target = document.querySelector('.product-form__buttons')
|| document.querySelector('.product-form__submit');
if (!target) return;
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
bar.classList.remove('is-visible');
bar.setAttribute('aria-hidden', 'true');
} else {
bar.classList.add('is-visible');
bar.setAttribute('aria-hidden', 'false');
}
});
}, { threshold: 0 });
observer.observe(target);
})();
</script>- Click Save
Notice what’s not in this snippet: no second form, no variant <select>, no AJAX code. The form="{{ product_form_id }}" attribute connects the button to Dawn’s real product form (Dawn names it product-form-SECTIONID), so submitting it behaves identically to clicking the main button β cart drawer and all.
Step 2: Add the CSS
Open Assets β base.css, scroll to the very bottom, and paste:
css
/* ===== Sticky Add to Cart Bar ===== */
.sticky-atc {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 50;
background: rgb(var(--color-background));
border-top: 1px solid rgba(var(--color-foreground), 0.1);
box-shadow: 0 -6px 20px rgba(0, 0, 0, 0.08);
transform: translateY(110%);
transition: transform 0.3s ease;
padding-bottom: env(safe-area-inset-bottom);
}
.sticky-atc.is-visible {
transform: translateY(0);
}
.sticky-atc__inner {
display: flex;
align-items: center;
gap: 1.2rem;
padding-top: 1rem;
padding-bottom: 1rem;
}
.sticky-atc__image {
width: 4.8rem;
height: 4.8rem;
object-fit: cover;
border-radius: 8px;
flex-shrink: 0;
}
.sticky-atc__details {
display: flex;
flex-direction: column;
min-width: 0;
flex: 1;
}
.sticky-atc__title {
font-size: 1.3rem;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.sticky-atc__price {
font-size: 1.3rem;
opacity: 0.85;
}
.sticky-atc__button {
flex-shrink: 0;
width: auto;
min-width: 14rem;
margin: 0;
}
@media (max-width: 749px) {
.sticky-atc__title {
font-size: 1.2rem;
}
.sticky-atc__button {
min-width: 12rem;
padding: 1rem 1.4rem;
}
}Click Save. Two details worth knowing:
env(safe-area-inset-bottom)keeps the bar clear of the iPhone home indicator β the touch that makes it feel native on mobile- The bar uses your theme’s CSS color variables, so it automatically matches your color scheme
Step 3: Render the Snippet on the Product Page
- Open Sections β main-product.liquid
- Scroll to the very bottom of the file β below the closing markup, above the
{% schema %}tag - Add:
liquid
{% render 'sticky-atc', product: product, section: section %}- Click Save
That’s the entire integration: one render line. The snippet is self-contained (markup, script) with its styles in base.css.
Step 4: Test the Behavior
Preview a product page and check the full behavior loop:
- On load: no bar β the real buy button is visible
- Scroll down past the buy button: the bar slides up from the bottom with image, title, price
- Tap the sticky button: the product is added and Dawn’s cart drawer opens β the giveaway that the form-attribute trick is working
- Select a different variant, then use the sticky button: the selected variant is added (again, because it’s the same form)
- Scroll back to the top: the bar slides away
- Sold-out product: the sticky button shows “Sold Out” and is disabled
- Mobile: the bar sits above the home indicator, the title truncates with an ellipsis instead of wrapping, and the button stays comfortably tappable
If all of that passes, publish your duplicate theme.
Customizations
Mobile-Only Sticky Bar
Some stores prefer sticky ATC only on phones, where the scroll problem is worst. One CSS addition:
css
@media (min-width: 990px) {
.sticky-atc {
display: none;
}
}Live Price Sync on Variant Change
The bar renders the current variant’s price on page load. If your variants have different prices and you want the sticky price to update live when the shopper switches variants, add this to the snippet’s script (inside the same function, after the observer code):
javascript
/* Mirror the theme's live price into the sticky bar */
var mainPrice = document.querySelector('.price__regular .price-item--regular')
|| document.querySelector('.price-item--regular');
var stickyPrice = document.getElementById('StickyATCPrice-{{ section.id }}');
if (mainPrice && stickyPrice) {
var priceObserver = new MutationObserver(function () {
stickyPrice.textContent = mainPrice.textContent.trim();
});
priceObserver.observe(mainPrice, { childList: true, subtree: true, characterData: true });
}The trick: Dawn already updates the main price element on every variant change, so instead of re-implementing that logic, we simply mirror it with a MutationObserver. Whatever the theme displays, the bar displays.
Styling Tweaks
- Accent button: the bar’s button inherits your theme’s button styles; give it your sale/accent color with
.sticky-atc__button { background: #e53935; }if you want it hotter than the main button - Top placement: change
bottom: 0totop: 0(and flip the transform totranslateY(-110%)and border toborder-bottom) for a header-style bar β bottom converts better on mobile, top is common on desktop - Show quantity or variant title: add
{{ current_variant.title }}under the product title in the details column for multi-variant clarity
Best Practices for Sticky Add to Cart Bars
Appear on scroll, not on load. A bar that duplicates a visible button is clutter. The IntersectionObserver behavior β appear only when the real button is gone β is the professional standard.
Keep it slim. One row: thumbnail, title, price, button. The bar’s job is access, not information. Anything taller starts eating mobile screen real estate shoppers need for your actual content.
Never cover important UI. Check the bar doesn’t overlap chat widgets, cookie banners, or your theme’s mobile navigation. Adjust z-index and bottom offsets so everything coexists.
Respect the sold-out state. A sticky bar cheerfully offering an unavailable product is worse than no bar. Our code disables and relabels the button automatically.
Test the cart drawer path on mobile. Sticky button β drawer opens β checkout reachable β walk the full purchase path on a real phone at least once.
Measure it. Note your mobile add-to-cart rate before launch and compare after two weeks. This pattern usually earns its place; let your data confirm it.
Common Mistakes to Avoid
- Building a second, separate add-to-cart form. It reloads to the cart page instead of opening the drawer, ignores the shopper’s variant selection, and doubles your maintenance. The
formattribute exists precisely so you don’t do this. - Showing the bar from page load. Redundant next to the visible buy button, and it trains shoppers to ignore it.
- Forgetting the safe-area inset. Without
env(safe-area-inset-bottom), the button collides with the iPhone home indicator and taps misfire. - A z-index war with the cart drawer. The bar should sit above page content but below the cart drawer and modals. Dawn’s drawer uses a high z-index; our
z-index: 50stays safely beneath it. If you raise it, re-test. - Editing the live theme. A stray character in
main-product.liquidbreaks every product page at once. Duplicate first, always. - Losing the code in a theme update. Updates replace
main-product.liquidandbase.css. Keep your three pieces (snippet, render line, CSS) saved β re-applying takes five minutes.
Troubleshooting
The bar never appears. The IntersectionObserver found no target. Your theme may name the buy-button wrapper differently β inspect the main Add to Cart button in your browser’s dev tools and update the querySelector lines to match its container class.
The bar appears but clicking the button does nothing. The form attribute doesn’t match your theme’s product form ID. Inspect the main product form in dev tools, note its id (Dawn: product-form-SECTIONID), and make sure product_form_id in the snippet builds the same string. Non-Dawn themes often use a different pattern.
Clicking the sticky button goes to the cart page instead of opening the drawer. Either the form attribute isn’t connected (see above, so the button falls back to other behavior), or your theme’s cart type is set to “page” β check Theme settings β Cart and select the drawer if you want drawer behavior.
The wrong variant gets added. Same root cause: a disconnected form attribute means the button isn’t submitting the real form. Once connected, variant selection is inherited automatically.
The bar overlaps my chat widget / cookie banner. Adjust the bar’s z-index, or add bottom offset spacing to whichever element should yield. There’s no universal answer β it depends what else floats on your pages.
The price doesn’t change when I switch variants. Expected with the base version (it renders the load-time price). Add the MutationObserver sync from the Customizations section, and verify the price selector matches your theme’s markup.
Everything works in preview but not live. You haven’t published the duplicate yet β or you’re seeing a cached page. Publish, then hard-refresh or test in incognito.
FAQ
1. How do I add a sticky Add to Cart button on Shopify without an app? Create a small snippet with a fixed bottom bar and a button linked to your theme’s existing product form via the HTML form attribute, plus an IntersectionObserver that shows the bar when the main buy button scrolls out of view. This guide includes the complete free code.
2. Does Shopify or the Dawn theme have a built-in sticky Add to Cart? No. Neither Shopify core nor Dawn ships a sticky ATC bar, which is why the App Store is full of paid options β and why this 25-minute customization is so popular.
3. Will the sticky button open the cart drawer like the normal button? Yes β that’s the point of the form attribute approach. The sticky button submits the same form as the main button, so the cart drawer, AJAX behavior, and variant selection all work identically.
4. Does it respect the variant the shopper selected? Yes. Because it submits the theme’s real product form, whatever variant and quantity the shopper chose in the main picker is exactly what gets added.
5. Will this slow down my product pages? No. It’s a few dozen lines of HTML/CSS and a tiny IntersectionObserver script β no libraries, no external requests, no measurable impact. That’s a genuine advantage over app-based sticky bars, which load external JavaScript.
6. Can I show the sticky bar only on mobile? Yes β one media query (display: none above 990px) hides it on desktop. Mobile is where the pattern earns most of its conversions anyway.
7. Should the bar be at the top or bottom of the screen? Bottom, in most cases β it sits in the natural thumb zone on phones. Top bars are a stylistic choice mostly seen on desktop-heavy stores; the CSS flip is included in this guide.
8. Does it handle sold-out products? Yes. The button renders as a disabled “Sold Out” state when the current variant is unavailable, mirroring the main button’s behavior.
9. Will this work on themes other than Dawn? The concept works everywhere; the two theme-specific pieces are the product form ID and the buy-button selector for the observer. On Sense, Craft, Refresh, and Studio the code works as-is; on other themes, inspect and adjust those two strings.
10. Does the price in the bar update when variants change? The base version shows the load-time price; the optional MutationObserver upgrade mirrors the theme’s live price element so the bar always matches the selected variant β with no duplicate pricing logic.
11. Do sticky Add to Cart bars actually increase conversions? On long product pages β especially mobile β yes, reliably enough that virtually every major eCommerce brand uses a persistent buy action. It shortens the path from decision to action, which is the whole conversion game.
12. Can I add quantity selection to the sticky bar? You can, but I’d advise against it β the bar should stay slim. Shoppers who care about quantity scroll to the main form; the bar serves the “yes, buy it” moment.
13. Will it conflict with my chat widget or cookie banner? Possibly β anything fixed to the bottom of the screen can collide. Adjust z-index and offsets so each element has its space, and test on mobile where space is tightest.
14. Will my sticky bar survive theme updates? The snippet file survives; the render line in main-product.liquid and the CSS in base.css get wiped by updates. Keep copies of all three pieces and re-apply after updating β about five minutes.
Conclusion
The sticky Add to Cart bar is conversion optimization at its most honest: it doesn’t pressure, flash, or count down β it just makes sure that when your shopper decides to buy, the button is already under their thumb.
And now you’ve built it properly. To recap: one snippet with the bar and its IntersectionObserver, one CSS block in base.css, one render line at the bottom of main-product.liquid. The form attribute connects your sticky button to the theme’s real product form β cart drawer, variant selection, and sold-out handling all inherited for free β and the bar appears only when it’s actually useful, sliding in as the main button scrolls away.
Twenty-five minutes, zero monthly fees, and one of the highest-impact mobile conversion upgrades a product page can get.
If you want to keep stacking improvements like this, the resources below are your next step.
Keep Learning and Get Help
Prefer to follow along on video? I walk through this build step by step on my YouTube channel, along with dozens of other app-free Shopify customizations: youtube.com/@foysalshopifyexpert
Want the ready-made version? If you’d rather skip the build, I sell a polished, fully featured Sticky Add to Cart system β along with popups, sliders, mega menus, and more plug-and-play Shopify sections β as one-time-purchase products with no subscriptions: ecommercethesis.gumroad.com
Want it installed for you? For hands-on help with sticky ATC, conversion optimization, or any Shopify development and customization project, hire me directly on Upwork: My Upwork Profile
Learn Shopify professionally. Turn customizations like this into freelancing income with my structured course: Freelancing with Shopify β or browse all courses.
Stuck on a step? Reach me directly on WhatsApp for personal help: wa.me/8801991505652
Happy selling β and may your buy button always be one tap away.