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>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.
Frequently Asked Questions
- How do I make a navbar stick to the top when scrolling?
- Set
position: stickyandtop: 0on the navbar. It scrolls with the page until it reaches the top of the viewport, then stays there. Add az-indexso content scrolling underneath does not overlap it. - Why is position sticky not working?
- Two usual causes. You have not set a
topvalue, which sticky requires, or an ancestor hasoverflow: hidden,autoorscroll, 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.