How to Create a Sticky Header with CSS
A sticky header keeps your navigation visible at the top of the page as users scroll down. It improves usability, keeps important links accessible, and gives your website a polished, modern feel.
In this complete guide, you will learn how to create a sticky header with CSS using two different approaches: position: sticky and position: fixed. We will walk through each method step by step, compare them side by side, and troubleshoot common issues like z-index conflicts and disappearing sticky behavior.
Whether you are building a simple landing page or a complex web application, this tutorial has you covered.
What Is a Sticky Header?
A sticky header is a navigation bar that stays pinned to the top of the browser window as the user scrolls through the page content. Instead of disappearing off-screen, the header “sticks” in place so visitors always have access to your menu, logo, and call-to-action buttons.
You have probably seen sticky headers on most modern websites. They are popular because they:
- Improve navigation on long pages
- Keep branding visible at all times
- Reduce the need to scroll back to the top
- Increase engagement with key links and CTAs
Two CSS Approaches: Sticky vs Fixed
Before we start coding, it is important to understand the two main CSS properties used to create sticky headers. They behave differently, and choosing the right one depends on your specific use case.
position: sticky
The element behaves like a relatively positioned element until it reaches a defined scroll threshold (like top: 0). Once you scroll past that point, it “sticks” in place. When you scroll back up, it returns to its normal document flow.
position: fixed
The element is removed from the normal document flow entirely and is positioned relative to the browser viewport. It stays in the same spot on screen no matter where the user scrolls.
Quick Comparison Table
| Feature | position: sticky | position: fixed |
|---|---|---|
| Stays in document flow | Yes (until scroll threshold) | No |
| Requires top/bottom offset | Yes | Optional |
| Needs spacer for content | No | Yes |
| Browser support | All modern browsers | All browsers |
| Best for | Headers below a hero section | Always-visible top headers |
| JavaScript required | No | No (but sometimes used for scroll effects) |
Method 1: Sticky Header with position: sticky (Recommended)
This is the most straightforward and modern way to create a sticky header with CSS. It requires just two lines of CSS and no JavaScript at all.
Step 1: Create the HTML Structure
Start with a basic page layout that includes a header and some content below it:
<header class="sticky-header">
<nav>
<a href="/">Logo</a>
<ul>
<li><a href="#home">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#services">Services</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</nav>
</header>
<main>
<section id="home">
<h1>Welcome to Our Website</h1>
<p>Scroll down to see the sticky header in action.</p>
</section>
<!-- More sections with enough content to scroll -->
</main>
Step 2: Apply the Sticky CSS
Now add the CSS to make the header stick to the top of the viewport when the user scrolls:
.sticky-header {
position: sticky;
top: 0;
background-color: #ffffff;
padding: 15px 30px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
z-index: 1000;
}
.sticky-header nav {
display: flex;
justify-content: space-between;
align-items: center;
}
.sticky-header ul {
display: flex;
list-style: none;
gap: 20px;
margin: 0;
padding: 0;
}
.sticky-header a {
text-decoration: none;
color: #333;
font-weight: 600;
}
That is it. The two critical lines are position: sticky; and top: 0;. Together, they tell the browser to keep the header pinned at the top once it reaches the top edge of the viewport.
Step 3: Test the Result
Open your page in a browser and scroll down. The header should remain visible at the top. When you scroll back up, it stays in its original position in the document flow without any layout shifts.
Method 2: Sticky Header with position: fixed
The position: fixed approach is older and more widely supported in legacy projects. The header is always locked to the top of the viewport from the moment the page loads.
Step 1: Use the Same HTML
You can use the same HTML structure as above. Just change the class name if you want to keep things organized:
<header class="fixed-header">
<nav>
<a href="/">Logo</a>
<ul>
<li><a href="#home">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#services">Services</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</nav>
</header>
<main class="main-content">
<!-- Your page content here -->
</main>
Step 2: Apply the Fixed CSS
.fixed-header {
position: fixed;
top: 0;
left: 0;
width: 100%;
background-color: #ffffff;
padding: 15px 30px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
z-index: 1000;
}
.main-content {
margin-top: 70px; /* Must match header height */
}
Step 3: Add the Content Spacer
This is a critical step that many developers forget. Because position: fixed removes the header from the document flow, the content below it will slide up and hide behind the header. You must add a top margin or padding to your main content area that matches the height of your header.
If your header height changes on different screen sizes, use a media query or calculate it dynamically with JavaScript.
Which Method Should You Use?
Here is a simple guide to help you decide:
- Use
position: stickywhen your header is part of the normal page layout and you want it to become sticky only after the user scrolls to it. This is the cleaner, more modern approach and does not require a content spacer. - Use
position: fixedwhen you need the header to be permanently locked to the top of the viewport from the very first pixel of scroll. This is common in single-page applications and dashboards.
For most standard websites in 2026, position: sticky is the recommended approach.
On-Scroll Sticky Header (Appears After Scrolling)
A popular design pattern is a header that only appears and sticks after the user scrolls past a certain point, like a hero banner. Here is how to build it with a small amount of JavaScript combined with CSS:
HTML
<div class="hero-banner">
<h1>Welcome</h1>
</div>
<header class="scroll-header hidden">
<nav>Your navigation here</nav>
</header>
<main>
<!-- Page content -->
</main>
CSS
.scroll-header {
position: fixed;
top: 0;
left: 0;
width: 100%;
background: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
z-index: 1000;
transition: transform 0.3s ease;
}
.scroll-header.hidden {
transform: translateY(-100%);
}
.scroll-header.visible {
transform: translateY(0);
}
JavaScript
const header = document.querySelector('.scroll-header');
const triggerPoint = 300; // pixels scrolled before header appears
window.addEventListener('scroll', () => {
if (window.scrollY > triggerPoint) {
header.classList.add('visible');
header.classList.remove('hidden');
} else {
header.classList.add('hidden');
header.classList.remove('visible');
}
});
This gives you a smooth slide-down animation when the header appears, and it slides back up when the user scrolls back to the top.
Handling Common Sticky Header Issues
Sticky headers can break or behave unexpectedly for several reasons. Here are the most common problems and how to fix them.
1. z-index Conflicts
Your sticky header might appear behind other elements like images, modals, or dropdowns. The fix is straightforward:
.sticky-header {
z-index: 1000;
position: relative; /* or sticky/fixed */
}
Important: z-index only works on positioned elements. Make sure any element you assign a z-index to also has a position value other than static.
If you have multiple layered components (modals, tooltips, dropdowns), establish a z-index scale:
| Element | Suggested z-index |
|---|---|
| Background content | 1 |
| Sticky header | 1000 |
| Dropdown menus | 1010 |
| Modal overlays | 2000 |
| Tooltips | 3000 |
2. position: sticky Not Working
This is one of the most common frustrations. If your sticky header refuses to stick, check these things in order:
- Missing
topvalue. You must specifytop: 0;(or another offset). Without it, the browser does not know where to stick the element. - Parent has
overflow: hiddenoroverflow: auto. This is the number one reason sticky fails. The sticky element can only stick within its parent container. If the parent clips overflow, the sticky behavior is cancelled. Remove or change the overflow property on all ancestor elements. - Parent container is too short. The sticky element sticks within its parent. If the parent is only as tall as the header itself, there is nowhere for it to stick. Make sure the parent container is taller than the sticky element.
- Ancestor has a
containproperty. CSScontain: paintorcontain: layouton an ancestor can break sticky positioning.
3. Content Jumping with position: fixed
When you use position: fixed, the header leaves the document flow. The content below jumps up by the height of the header. To prevent this:
body {
padding-top: 70px; /* Same as header height */
}
Or use JavaScript to calculate the header height dynamically:
const header = document.querySelector('.fixed-header');
document.body.style.paddingTop = header.offsetHeight + 'px';
4. Sticky Header Covers Anchor Links
When clicking anchor links (like #about), the browser scrolls to that section, but the sticky header covers the top of it. Fix this with the scroll-margin-top property:
section {
scroll-margin-top: 80px; /* Slightly more than header height */
}
Making Your Sticky Header Responsive
On smaller screens, a full navigation bar might take up too much vertical space. Here are some best practices for responsive sticky headers:
- Reduce header height on mobile. Use smaller padding and font sizes within a media query.
- Switch to a hamburger menu. Collapse your navigation links into a mobile menu to save space.
- Consider hiding the header on scroll down. Show it again when the user scrolls up. This gives users more screen real estate while still providing quick access to navigation.
Here is an example of responsive adjustments:
@media (max-width: 768px) {
.sticky-header {
padding: 10px 15px;
}
.sticky-header ul {
display: none; /* Hidden by default, shown via JS hamburger toggle */
}
}
Adding a Background Change on Scroll
A nice visual touch is changing the header background or adding a shadow once the user starts scrolling. This can be done with a few lines of JavaScript:
window.addEventListener('scroll', () => {
const header = document.querySelector('.sticky-header');
if (window.scrollY > 50) {
header.classList.add('scrolled');
} else {
header.classList.remove('scrolled');
}
});
.sticky-header {
position: sticky;
top: 0;
background: transparent;
transition: background 0.3s ease, box-shadow 0.3s ease;
z-index: 1000;
}
.sticky-header.scrolled {
background: #ffffff;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12);
}
This creates a smooth transition from a transparent header to a white header with a subtle shadow as the user scrolls.
Complete Copy-Paste Example
Here is a full working example you can copy into an HTML file and open in your browser to test:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sticky Header Demo</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
line-height: 1.6;
}
.sticky-header {
position: sticky;
top: 0;
background: #2c3e50;
color: #fff;
padding: 15px 30px;
z-index: 1000;
}
.sticky-header nav {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: 0 auto;
}
.sticky-header ul {
display: flex;
list-style: none;
gap: 25px;
}
.sticky-header a {
color: #fff;
text-decoration: none;
}
section {
padding: 80px 30px;
max-width: 1200px;
margin: 0 auto;
scroll-margin-top: 60px;
}
section:nth-child(even) {
background: #f4f4f4;
}
</style>
</head>
<body>
<header class="sticky-header">
<nav>
<a href="/"><strong>MySite</strong></a>
<ul>
<li><a href="#home">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#services">Services</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</nav>
</header>
<main>
<section id="home">
<h1>Home Section</h1>
<p>Scroll down to see the sticky header in action.</p>
</section>
<section id="about">
<h2>About Section</h2>
<p>This section has enough height to demonstrate scrolling.</p>
</section>
<section id="services">
<h2>Services Section</h2>
<p>Keep scrolling. The header stays at the top.</p>
</section>
<section id="contact" style="min-height: 100vh;">
<h2>Contact Section</h2>
<p>The sticky header is still visible up there.</p>
</section>
</main>
</body>
</html>
Accessibility Tips for Sticky Headers
A sticky header should not create problems for users who rely on keyboards or screen readers. Keep these points in mind:
- Make sure all navigation links are keyboard-accessible (use proper
<a>and<button>elements). - Use semantic HTML: wrap your navigation in a
<header>and<nav>element. - Add
aria-label="Main navigation"to your<nav>for screen reader clarity. - Ensure sufficient color contrast between header text and background.
- Do not let the sticky header block too much content on small viewports.
Frequently Asked Questions
How do I make a header sticky in CSS?
Add position: sticky; and top: 0; to your header element. This tells the browser to keep the header pinned at the top of the viewport once the user scrolls past its original position. Make sure no parent element has overflow: hidden set, as that will break the sticky behavior.
What is the difference between position sticky and position fixed?
position: sticky keeps the element in the normal document flow until it hits a scroll threshold, then pins it in place. position: fixed removes the element from the document flow entirely and keeps it locked to the viewport at all times. Sticky is generally preferred for headers because it does not require a content spacer.
Why is my sticky header not working?
The most common causes are: a missing top value, a parent element with overflow: hidden or overflow: auto, a parent container that is not tall enough, or a CSS contain property on an ancestor. Check each of these and the sticky behavior should start working.
How do I prevent a sticky header from covering anchor link targets?
Use the CSS property scroll-margin-top on your target sections. Set the value slightly larger than your header height. For example: scroll-margin-top: 80px;.
Can I make a sticky header only appear after scrolling?
Yes. Use position: fixed combined with a CSS class that hides the header by default (using transform: translateY(-100%)). Then use JavaScript to add a visible class when the user scrolls past a certain point.
Is position: sticky supported in all browsers?
As of 2026, position: sticky is supported in all modern browsers including Chrome, Firefox, Safari, Edge, and their mobile versions. It has had excellent support for several years now, so there is no reason to avoid using it for new projects.
Wrapping Up
Creating a sticky header with CSS is one of those tasks that sounds complicated but turns out to be surprisingly simple once you know the right properties to use. For most websites, position: sticky with top: 0 is all you need. For more advanced use cases like scroll-triggered headers, combining position: fixed with a bit of JavaScript gives you full control.
Remember to always test your sticky header across different screen sizes, check for z-index conflicts with other page elements, and keep accessibility in mind. A well-implemented sticky header makes your website easier and more enjoyable to navigate.