Shopify Sticky Header on Scroll (Copy-Paste Code, No Apps)

A good sticky header should stay out of the way while keeping navigation close. The pattern most modern shops use: transparent over the hero, then becomes solid, smaller, and shadowed after you scroll. Bonus UX: hide on scroll-down to give content space, reveal on scroll-up so customers can navigate instantly.

Sticky Header

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



  1. Online Store → Themes → Customize
  2. Open your page template (Home is typical)
  3. Add section → Custom HTML (or Custom Liquid) at the very top
  4. Paste the entire snippet below
  5. 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>.

DESIGNED FOR REFERENCE ONLY
<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.