Below is a pure HTML/CSS/JS implementation that drops into Shopify without Liquid or apps. Treat it as an example — adjust colors, heights, thresholds, and behavior to match your theme.
Where to paste this in Shopify
- Online Store → Themes → Customize
- Open your page template (Home is typical)
- Add section → Custom HTML (or Custom Liquid) at the very top
- Paste the entire snippet below
- Save and preview
If your theme already has a header, place this demo header instead (for learning) or adapt the JS/CSS classes to your theme’s header element.
Copy-Paste Example — Sticky Header on Scroll
Paste everything from <!DOCTYPE html> to </html>.
<body>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shopify Sticky Header on Scroll</title>
<style>
:root{
/* Tweak these tokens to match your brand */
--hdr-height: 78px; /* default header height */
--hdr-height-stuck: 60px; /* height after sticky shrink */
--hdr-bg: rgba(15,17,21,0.0); /* transparent over hero at top */
--hdr-bg-stuck: #0f1115; /* solid after sticking */
--hdr-fg: #e8ecf1; /* text/icon color */
--hdr-accent: #00d084; /* CTA color */
--hdr-shadow: 0 10px 24px rgba(0,0,0,.22);
--z-hdr: 100000; /* on top of most things */
--container: 1200px;
--pad: 16px;
--threshold: 120; /* px scrolled before header sticks */
--reveal-gap: 6; /* px delta to decide up vs down */
}
/* Demo page setup (safe to remove) */
html,body{ margin:0; background:#0b0d12; color:#e8ecf1; font-family: Arial, sans-serif; }
.hero{
min-height: 74vh;
background: url('https://picsum.photos/id/1018/2000/1200') center/cover no-repeat;
display:grid; place-items:center;
text-align:center; padding:80px 16px 40px;
}
.hero h1{ margin:0 0 10px; font-size: clamp(28px, 6vw, 60px); text-shadow: 0 6px 30px rgba(0,0,0,.45); }
.hero p{ max-width: 64ch; margin:0 auto 18px; opacity:.95; text-shadow: 0 3px 18px rgba(0,0,0,.4); }
.filler{ padding: 30px 16px 1600px; max-width: var(--container); margin: 0 auto; color:#cfd7e3; }
/* ===== HEADER ===== */
.header{
position: sticky; top: 0; left: 0; right: 0;
height: var(--hdr-height);
display: grid; align-items: center;
z-index: var(--z-hdr);
transition: background .25s ease, height .18s ease, transform .18s ease, box-shadow .18s ease;
will-change: transform, background, height;
background: var(--hdr-bg);
backdrop-filter: saturate(120%) blur(0px);
}
.header__inner{
height: 100%;
display:flex; align-items:center; justify-content:space-between; gap:12px;
max-width: var(--container); margin: 0 auto; padding: 0 var(--pad);
}
.logo{ font-weight: 900; letter-spacing:.2px; font-size: 18px; color: var(--hdr-fg); text-decoration:none; }
.nav{ display:flex; align-items:center; gap: 18px; }
.nav a{ color: var(--hdr-fg); text-decoration:none; opacity:.92; font-weight:600; }
.cta{
appearance:none; border:0; border-radius: 10px; padding: 10px 14px; font-weight:800;
background: var(--hdr-accent); color: #0f1115; cursor:pointer;
}
/* Stuck (after threshold) */
.header.is-stuck{
height: var(--hdr-height-stuck);
background: var(--hdr-bg-stuck);
box-shadow: var(--hdr-shadow);
backdrop-filter: saturate(140%) blur(6px);
}
/* Hidden while scrolling down */
.header.is-hidden{
transform: translateY(-100%);
}
/* Optional: contrast helpers for transparent over hero */
.header.is-top{
background: var(--hdr-bg); /* stay transparent at very top */
box-shadow: none;
backdrop-filter: none;
}
/* Mobile tweaks */
@media (max-width: 860px){
:root{ --hdr-height: 70px; --hdr-height-stuck: 56px; }
.nav{ display:none; }
}
</style>
<!-- STICKY HEADER -->
<header class="header is-top" id="stickyHeader" data-threshold="120" when to become>
data-hide-on-down="true" <!-- hide when user scrolls down -->
data-reveal-gap="6" <!-- min px delta to treat as scroll up/down -->
data-solid-after-hero="true" <!-- go solid when not at page top -->
aria-label="Primary">
<div class="header__inner">
<a class="logo" href="/">ALT//SHOP</a>
<nav class="nav" aria-label="Main">
<a href="/collections/all">Shop</a>
<a href="/pages/about">About</a>
<a href="/pages/contact">Contact</a>
</nav>
<button class="cta" onclick="window.location.href='/cart'">View Cart</button>
</div>
</header>
<!-- DEMO HERO (transparent header sits over this) -->
<section class="hero">
<div>
<h1>Fall Capsule Release</h1>
<p>Premium textures, modern silhouettes, and motion-aware UX. Scroll to see the header behavior: transparent at top, solid + smaller after you scroll.</p>
<button class="cta" onclick="window.location.href='/collections/all'">Shop New</button>
</div>
</section>
<!-- DEMO PAGE CONTENT -->
<div class="filler">
<h2>Content Section</h2>
<p>This filler content lets you test hide-on-down and reveal-on-up. Keep scrolling and watch the header shrink and add a shadow after the threshold.</p>
</div>
<script>
(function(){
const hdr = document.getElementById('stickyHeader');
if(!hdr) return;
const threshold = parseInt(hdr.getAttribute('data-threshold') || getCssVar('--threshold') || '120', 10);
const hideOnDown = (hdr.getAttribute('data-hide-on-down') || 'true') === 'true';
const revealGap = parseInt(hdr.getAttribute('data-reveal-gap') || getCssVar('--reveal-gap') || '6', 10);
const solidAfterHero = (hdr.getAttribute('data-solid-after-hero') || 'true') === 'true';
let lastY = window.scrollY || 0;
let stuck = false;
function getCssVar(name){
return getComputedStyle(document.documentElement).getPropertyValue(name).trim().replace('px','');
}
function applyTopState(y){
// If at very top (<= 2px), keep the transparent "is-top" look
const atTop = y <= 2;
hdr.classList.toggle('is-top', atTop);
}
function evaluate(){
const y = window.scrollY || window.pageYOffset || 0;
// Stuck state
if(!stuck && y >= threshold){
stuck = true;
hdr.classList.add('is-stuck');
} else if(stuck && y < threshold){
stuck = false;
hdr.classList.remove('is-stuck');
}
// Transparent at top vs solid after hero (if enabled)
if(solidAfterHero){
applyTopState(y);
}
// Hide on down / show on up
if(hideOnDown && y > threshold){
if(y > lastY + revealGap){
hdr.classList.add('is-hidden'); // scrolling down
} else if(y < lastY - revealGap){
hdr.classList.remove('is-hidden'); // scrolling up
}
} else {
hdr.classList.remove('is-hidden');
}
lastY = y;
}
// Init
evaluate();
window.addEventListener('scroll', evaluate, {passive:true});
window.addEventListener('resize', evaluate, {passive:true});
window.addEventListener('orientationchange', evaluate, {passive:true});
})();
</script>
</body>
How it works (quick breakdown)
- HTML: A simple header with a logo, nav, and CTA. Data-attributes control behavior (threshold, hide-on-down, etc.).
- CSS:
- position: sticky; top: 0 pins the header.
- .is-stuck shrinks the height, sets a solid background, and adds a shadow.
- .is-hidden translates the header out of view during scroll-down.
- .is-top keeps the header transparent over the hero when at page top.
- JS: Watches scroll position/direction, toggles classes, and respects small deltas via data-reveal-gap to avoid flicker.
Customize it fast
- Heights: --hdr-height and --hdr-height-stuck.
- Colors: --hdr-bg (transparent RGBA at top), --hdr-bg-stuck (solid), --hdr-fg, --hdr-accent.
- Shadow: --hdr-shadow (or remove for flat style).
- Threshold: change data-threshold="160" to stick later/earlier.
- Behavior: set data-hide-on-down="false" to keep it always visible; set data-solid-after-hero="false" if you don’t want transparency at top.
- Z-index: bump --z-hdr if your theme’s elements overlap.
- Integrate with your theme’s header: move the id="stickyHeader" onto the theme’s header element and copy the class names (is-stuck, is-hidden, is-top) into your existing CSS.
Best practices
- Keep the sticky height modest (50–64px) to maximize reading space.
- Ensure contrast for readability after it turns solid.
- Test with your theme’s sticky announcement bars; if you have one, increase the header’s top spacing or disable hide-on-down to avoid jumpiness.
- On mobile, removing the main nav (hamburger only) keeps the header compact.
Troubleshooting
- Header overlaps content → Add top padding to the first section or set a taller --hdr-height.
- Flicker on small scrolls → Increase data-reveal-gap (e.g., 10).
- Never turns solid → Make sure data-solid-after-hero="true" and you actually scrolled (not at the very top).
- Competes with another sticky header → Use just one; or merge classes/JS into the theme’s existing header.