How to Make a Sticky Navbar in CSS

This recipe pins a navbar to the top of the viewport as the page scrolls, using position: sticky and top: 0 instead of position: fixed.

Sticky versus fixed

position: fixed removes an element from the normal document flow entirely and pins it to the viewport at all times, from the moment the page loads - which usually means adding manual padding to whatever sits underneath so fixed content does not cover it. position: sticky is gentler: the element stays in normal flow, scrolling with the page like any other box, until the scroll position would carry it past the offset you specify - then it sticks in place, behaving like fixed, until its containing block scrolls out of view entirely.

The rule: position: sticky plus top: 0

Sticky positioning needs an edge offset to do anything - top: 0 means stick once the element reaches the top of its nearest scrolling ancestor. Without a top, right, bottom, or left value, sticky behaves exactly like static and never sticks. The parent also has to allow it: overflow: hidden or overflow: auto on an ancestor clips the sticky effect, because the element can never stick past the edge of a container that hides its own overflow.

Example

<!DOCTYPE html>
<html>
<head>
<style>
  body { margin: 0; font-family: sans-serif; }
  .navbar {
    position: sticky;
    top: 0;
    background: #1f2937;
    color: white;
    padding: 16px 24px;
    z-index: 10;
  }
  .navbar ul { display: flex; gap: 20px; list-style: none; margin: 0; padding: 0; }
  .navbar a { color: white; text-decoration: none; }
  section { padding: 60px 24px; min-height: 60vh; }
  section:nth-child(odd) { background: #f9fafb; }
</style>
</head>
<body>
  <nav class='navbar'>
    <ul>
      <li><a href='#'>Home</a></li>
      <li><a href='#'>Docs</a></li>
      <li><a href='#'>Pricing</a></li>
    </ul>
  </nav>
  <section>
    <h2>Intro section</h2>
    <p>Scroll down - the navbar stays at the top of the viewport instead of scrolling away with the page.</p>
  </section>
  <section>
    <h2>Second section</h2>
    <p>Because the navbar's containing block is the whole page, it keeps sticking all the way to the bottom.</p>
  </section>
</body>
</html>
Note: If a sticky element refuses to stick, check its ancestors for overflow: hidden or overflow: auto first - that is the most common reason position: sticky silently does nothing.

A shadow that appears once you have scrolled

A small enhancement makes a sticky navbar easier to notice: add a class once the user has scrolled past the very top of the page, and use that class to add a shadow or background so the stuck navbar visually separates itself from the page underneath. A single scroll listener checking window.scrollY is enough - no scroll-position math needed.

Example

<!DOCTYPE html>
<html>
<head>
<style>
  .navbar {
    position: sticky;
    top: 0;
    background: white;
    padding: 16px 24px;
    transition: box-shadow 0.2s ease;
  }
  .navbar.is-stuck {
    box-shadow: 0 2px 8px rgba(0,0,0,0.15);
  }
</style>
</head>
<body>
  <nav class='navbar' id='navbar'>My Site</nav>
  <div style='height: 1500px;'></div>

  <script>
    const navbar = document.getElementById('navbar');
    window.addEventListener('scroll', () => {
      navbar.classList.toggle('is-stuck', window.scrollY > 0);
    });
  </script>
</body>
</html>

When fixed is still the right call

position: fixed is still the better tool when an element must be pinned regardless of where it sits in the document - a floating action button, a cookie banner, or a navbar that should overlay content from the very first pixel of the page rather than only once scrolled to. In that case, remember to add padding-top (or a margin) to whatever comes first in the body, or the fixed element will simply sit on top of it.

ValueStays in normal flow?Pinned relative toCommon use
static (default)YesNowhere - just document orderOrdinary elements
relativeYes, offsets are only visualIts own normal positionSmall nudges, or a positioning context for absolute children
sticky; top: 0Yes, until it would scroll past the offsetIts nearest scrolling ancestorNavbars and section headers that should stay visible while their section is on screen
fixedNo - removed from flowThe viewport, alwaysAlways-on overlays: floating buttons, banners, navbars pinned from page load

Frequently Asked Questions

How do I make a navbar stick to the top when scrolling?
Set position: sticky and top: 0 on the navbar. It scrolls with the page until it reaches the top of the viewport, then stays there. Add a z-index so content scrolling underneath does not overlap it.
Why is position sticky not working?
Two usual causes. You have not set a top value, which sticky requires, or an ancestor has overflow: hidden, auto or scroll, which confines sticky to that container. Remove the overflow, or move the navbar outside it.
What is the difference between sticky and fixed?
A fixed element leaves the document flow immediately and never moves. A sticky element stays in flow and scrolls normally until it hits its offset, then pins. Sticky keeps its space in the layout; fixed does not, which is why fixed headers often overlap content.