How to Create a Fixed Navbar on Scroll With CSS Easily

How to Create a Fixed Navbar on Scroll With CSS Easily - Image

A navigation bar should help visitors move around your website without making them search for it again after every scroll. A fixed or sticky navbar solves that problem by keeping important navigation links accessible as the page moves.

The original approach to creating a fixed navbar often relies on Bootstrap, jQuery, and a custom scroll handler. That method can work, but modern CSS gives you a much simpler option for many websites. position: sticky can keep a navigation bar at the top of the page while allowing it to remain part of the document flow. For situations where the header must always stay attached to the viewport, position: fixed is still useful.

This guide explains both approaches, shows complete HTML and CSS examples, and covers the common problems that can make a fixed header overlap content or behave incorrectly on mobile devices.

Fixed Navbar vs Sticky Navbar: What's the Difference?

Before writing any JavaScript, decide what behavior your website actually needs. A fixed navbar and a sticky navbar can look almost identical, but they work differently. Also check the process of upload Image without page reload using Jquery.

How a Fixed Navbar Works

A fixed navbar is positioned relative to the browser viewport. When you use position: fixed with top: 0, the navigation stays at the top of the visible screen even while the document continues scrolling.

A basic example looks like this:

.navbar {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    z-index: 1000;
}

Because a fixed element is removed from the normal document flow, the content underneath it can move upward and become hidden behind the header. You therefore need to account for the header's height in your page layout.

How a Sticky Navbar Works

A sticky navbar is usually the better starting point for a modern website.

.navbar {
    position: sticky;
    top: 0;
    z-index: 1000;
}

With position: sticky, the element behaves normally until it reaches the specified threshold. Once it reaches top: 0, it sticks to that position while its containing area allows it to remain there. MDN describes sticky positioning as a hybrid between normal/relative positioning and fixed-style behavior.

For a simple navigation bar that should remain visible after reaching the top, this can eliminate the need for JavaScript entirely.

The Simplest Fixed Navbar Using Modern CSS

Let's start with a complete solution that requires no jQuery and no scroll event.

HTML Structure

Use semantic HTML for the navigation:

<header class="site-header">
    <nav class="navbar" aria-label="Main navigation">
        <a class="logo" href="/">My Website</a>

        <ul class="nav-links">
            <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 My Website</h1>
        <p>Your page content goes here.</p>
    </section>

    <section id="about">
        <h2>About</h2>
        <p>More content goes here.</p>
    </section>

    <section id="services">
        <h2>Services</h2>
        <p>More content goes here.</p>
    </section>

    <section id="contact">
        <h2>Contact</h2>
        <p>More content goes here.</p>
    </section>
</main>

CSS for a Sticky Header

* {
    box-sizing: border-box;
}

html {
    scroll-behavior: smooth;
}

body {
    margin: 0;
    font-family: Arial, sans-serif;
    color: #222;
    line-height: 1.6;
}

.site-header {
    position: sticky;
    top: 0;
    z-index: 1000;
}

.navbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 30px;
    padding: 18px 6%;
    background: #ffffff;
    border-bottom: 1px solid #e5e5e5;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
}

.logo {
    color: #111;
    font-size: 1.4rem;
    font-weight: 700;
    text-decoration: none;
}

.nav-links {
    display: flex;
    gap: 24px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.nav-links a {
    color: #222;
    text-decoration: none;
    font-weight: 600;
}

.nav-links a:hover {
    color: #2563eb;
}

main section {
    min-height: 700px;
    padding: 80px 6%;
}

That's enough to create a sticky navigation bar for many websites.

There is no jQuery dependency, no Bootstrap dependency, and no custom window.onscroll function. The browser handles the sticky positioning itself.

When Should You Use position: fixed?

position: sticky is not a replacement for every fixed-header design.

Use position: fixed when the header needs to remain attached to the viewport regardless of its normal position in the document. A persistent utility bar, floating navigation control, or permanently visible application toolbar can be a good example.

Fixed Navbar Example

.site-header {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    z-index: 1000;
}

main {
    padding-top: 80px;
}

The extra top spacing is important because a fixed element no longer occupies its original space in normal document flow. Without compensation, the first part of your page can disappear beneath the header.

If your header height changes on smaller screens, avoid hard-coding a value that can become incorrect. A CSS variable can make the relationship clearer:

:root {
    --header-height: 72px;
}

.site-header {
    position: fixed;
    inset: 0 0 auto;
    min-height: var(--header-height);
}

main {
    padding-top: var(--header-height);
}

How to Add a Shrinking Navbar on Scroll

A common design does more than simply keep the navbar visible. The header starts large and comfortable, then becomes smaller after the visitor begins scrolling.

This is where JavaScript can make sense.

The original implementation on Legend Blogs uses a scroll handler to add and remove a fixed-theme class, changing the header background, brand size, and container padding. It also depends on older jQuery and Bootstrap assets.

A cleaner implementation can use one CSS class and modern browser APIs.

HTML

<header class="site-header" id="site-header">
    <nav class="navbar">
        <a href="/" class="logo">My Website</a>

        <ul class="nav-links">
            <li><a href="#home">Home</a></li>
            <li><a href="#about">About</a></li>
            <li><a href="#contact">Contact</a></li>
        </ul>
    </nav>
</header>

CSS

.site-header {
    position: sticky;
    top: 0;
    z-index: 1000;
}

.navbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 22px 6%;
    background: white;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
    transition: padding 0.25s ease,
                background-color 0.25s ease,
                box-shadow 0.25s ease;
}

.logo {
    font-size: 1.5rem;
    font-weight: 700;
    transition: font-size 0.25s ease;
}

.site-header.is-scrolled .navbar {
    padding: 12px 6%;
    background: #111827;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}

.site-header.is-scrolled .logo {
    font-size: 1.15rem;
}

.site-header.is-scrolled a {
    color: white;
}

JavaScript

const header = document.querySelector("#site-header");

function updateHeader() {
    header.classList.toggle("is-scrolled", window.scrollY > 40);
}

window.addEventListener("scroll", updateHeader, { passive: true });

updateHeader();

This is considerably easier to understand than maintaining a custom object with separate add() and remove() methods.

Remember that scroll events can fire at a high rate. Expensive DOM operations inside a scroll handler can cause jank, so keep the handler lightweight. MDN also recommends considering throttling or alternatives such as IntersectionObserver when appropriate.

A Better Approach: Use IntersectionObserver

If the purpose of JavaScript is simply to detect when the page has moved beyond the hero section, you don't necessarily need to continuously inspect window.scrollY.

An invisible marker can be observed instead.

HTML

<div id="header-trigger" aria-hidden="true"></div>

<header class="site-header" id="site-header">
    <nav class="navbar">
        <a href="/" class="logo">My Website</a>
        <ul class="nav-links">
            <li><a href="#home">Home</a></li>
            <li><a href="#about">About</a></li>
            <li><a href="#contact">Contact</a></li>
        </ul>
    </nav>
</header>

JavaScript

const header = document.querySelector("#site-header");
const trigger = document.querySelector("#header-trigger");

const observer = new IntersectionObserver(
    ([entry]) => {
        header.classList.toggle("is-scrolled", !entry.isIntersecting);
    },
    {
        threshold: 0
    }
);

observer.observe(trigger);

IntersectionObserver lets the browser notify your code when an element enters or leaves an intersection with the viewport instead of requiring you to repeatedly calculate its position during scrolling.

This is particularly useful when your header changes state after the visitor passes a specific section.

Common Problems With Fixed and Sticky Navbars

A navbar can look correct in a desktop demo and still fail on a real website. These are the issues worth checking first.

Content Hides Behind a Fixed Header

This happens because fixed positioning removes the element from normal document flow.

Use appropriate top spacing:

:root {
    --header-height: 70px;
}

header {
    height: var(--header-height);
}

main {
    padding-top: var(--header-height);
}

For a sticky header, this particular compensation is usually unnecessary because the element retains its place in the layout.

position: sticky Does Not Work

Check the navbar's ancestors.

Sticky positioning is affected by the nearest ancestor that establishes a scrolling mechanism. Ancestors with overflow: hidden, auto, scroll, or overlay can change the behavior you expect.

Also make sure you actually specify a threshold:

.navbar {
    position: sticky;
    top: 0;
}

Without top, bottom, or another relevant inset value, sticky positioning may behave like normal relative positioning.

The Navbar Appears Behind Other Elements

Set an appropriate stacking order:

.navbar {
    position: sticky;
    top: 0;
    z-index: 1000;
}

Avoid automatically using an extremely large z-index everywhere. A consistent stacking strategy is easier to maintain.

Anchor Links Jump Under the Header

A sticky header can cover a section heading when a visitor clicks an in-page link.

One simple solution is:

section {
    scroll-margin-top: 90px;
}

This tells the browser to leave space above the target when scrolling to it.

The Header Becomes Too Large on Mobile

A desktop navigation with six or seven links may not fit comfortably on a small screen.

Use a responsive layout rather than forcing everything into one row:

@media (max-width: 700px) {
    .navbar {
        flex-direction: column;
        align-items: flex-start;
        gap: 12px;
        padding: 14px 5%;
    }

    .nav-links {
        width: 100%;
        gap: 14px;
        overflow-x: auto;
    }

    .nav-links a {
        white-space: nowrap;
    }
}

For more complex navigation, a proper mobile menu button is preferable to a horizontally overflowing list.

Fixed Navbar Accessibility Tips

A navigation bar should remain usable, not merely visible.

Use semantic <nav> markup and provide an accessible label when a page contains multiple navigation regions:

<nav aria-label="Main navigation">
    ...
</nav>

Keep keyboard focus visible. Avoid removing outlines without replacing them with another clear focus indicator.

.nav-links a:focus-visible {
    outline: 3px solid #2563eb;
    outline-offset: 4px;
}

Also make sure the header does not obscure content when users zoom the page. Fixed and sticky elements can create accessibility problems when they cover information or become difficult to use at larger text sizes.

Fixed Navbar With a Modern Responsive Example

For most new projects, this is a strong baseline:

<header class="site-header">
    <nav class="navbar" aria-label="Main navigation">
        <a class="logo" href="/">Brand</a>

        <ul class="nav-links">
            <li><a href="#home">Home</a></li>
            <li><a href="#features">Features</a></li>
            <li><a href="#about">About</a></li>
            <li><a href="#contact">Contact</a></li>
        </ul>
    </nav>
</header>

<main>
    <section id="home">
        <h1>Modern Website Navigation</h1>
    </section>

    <section id="features">
        <h2>Features</h2>
    </section>

    <section id="about">
        <h2>About Us</h2>
    </section>

    <section id="contact">
        <h2>Contact</h2>
    </section>
</main>
* {
    box-sizing: border-box;
}

html {
    scroll-behavior: smooth;
}

body {
    margin: 0;
    font-family: system-ui, sans-serif;
    color: #172033;
}

.site-header {
    position: sticky;
    top: 0;
    z-index: 1000;
}

.navbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    max-width: 1200px;
    margin: 0 auto;
    padding: 16px 24px;
    background: rgba(255, 255, 255, 0.96);
    border-bottom: 1px solid #e5e7eb;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
}

.logo {
    color: #111827;
    font-size: 1.3rem;
    font-weight: 800;
    text-decoration: none;
}

.nav-links {
    display: flex;
    gap: 22px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.nav-links a {
    color: #374151;
    font-weight: 600;
    text-decoration: none;
}

.nav-links a:hover,
.nav-links a:focus-visible {
    color: #2563eb;
}

section {
    min-height: 700px;
    padding: 80px 24px;
    scroll-margin-top: 90px;
}

@media (max-width: 700px) {
    .navbar {
        padding: 14px 16px;
    }

    .nav-links {
        gap: 14px;
        max-width: 65%;
        overflow-x: auto;
    }
}

This approach keeps the implementation small while providing responsive behavior, semantic markup, keyboard focus styling, smooth navigation, and anchor offset support.

Should You Use JavaScript for a Fixed Navbar?

Usually, no.

If you only want a navbar to remain at the top while the user scrolls, start with CSS:

.navbar {
    position: sticky;
    top: 0;
}

Use position: fixed when the navigation genuinely needs to remain attached to the viewport independently of document flow.

Use JavaScript when you need behavior CSS alone does not provide, such as changing the header's appearance after the visitor passes a particular point, hiding the navbar while scrolling down, revealing it while scrolling up, or coordinating the header with other interactive components.

Modern browser APIs can handle these enhancements without requiring legacy jQuery code. The original tutorial uses jQuery and Bootstrap dependencies, whereas the examples above use native HTML, CSS, and JavaScript.

FAQ

How do I make a navbar fixed when scrolling?

Use position: fixed, top: 0, and width: 100% on the navbar. Because fixed elements leave normal document flow, add enough top spacing to the page content to prevent the header from covering it.

Is sticky better than fixed for a navbar?

For many websites, yes. position: sticky keeps the element in the document flow and makes it stick when it reaches the specified threshold. It is often the simplest solution for a navigation bar that should remain visible during scrolling.

Can I create a fixed navbar without JavaScript?

Yes. CSS alone is enough for a basic sticky or fixed navbar. JavaScript is only necessary when you want additional scroll-dependent behavior.

Why is my sticky navbar not working?

Check the top value and the navbar's parent elements. An ancestor with certain overflow settings can create a different scrolling context and affect sticky positioning.

How do I stop a fixed header from covering content?

Give the page content appropriate top spacing that accounts for the fixed header's height. For in-page links, scroll-margin-top can also prevent headings from being hidden behind the header.

Does a fixed navbar affect website performance?

A simple fixed or sticky navbar is generally lightweight, but complex visual effects and expensive scroll handlers can introduce unnecessary work. Keep scroll callbacks small and avoid repeatedly performing costly DOM operations.

Should I use Bootstrap or jQuery for a fixed navbar?

You don't need either for a basic implementation. Modern CSS provides position: sticky and position: fixed, while native JavaScript can handle optional interactive behavior without adding those dependencies.

Conclusion

Creating a fixed navbar on scroll no longer requires a complicated combination of Bootstrap, jQuery, and custom scroll logic. For most websites, position: sticky is the cleanest place to start because it provides persistent navigation while keeping the element within the page layout.

Use position: fixed when the header genuinely needs to stay attached to the viewport. If you want a shrinking, color-changing, or otherwise dynamic header, add a small amount of JavaScript and let CSS handle the visual transitions.

The most important improvement is to choose the simplest technique that matches the desired behavior. A lightweight, responsive, accessible navbar is easier to maintain—and usually provides a better experience than a solution built around unnecessary JavaScript.

Related posts

(2) Comments

  • User Pic

    Yes, you can use header tag.

  • User Pic

    Can i use sticky header with

    tag?

Write a comment