Webflow

How to Fix Anchor Links Hiding Behind Your Sticky Navbar in Webflow

TS Talha Shahzad··9 min read
The short version
  • Position sticky does not auto-adjust scroll offset for anchor links
  • scroll-padding-top on the html element is the cleanest one-line CSS fix
  • Use scroll-margin-top on individual sections when offsets need to vary
  • Set different offsets for mobile where nav height changes
  • JavaScript offset is only needed when nav height is dynamic

The fix is one line of CSS: add scroll-padding-top to your html element with a value matching your navbar height. That is it. No JavaScript, no hacky spacer divs, no workarounds.

If your navbar is 80px tall, add this to your site-wide custom code in the head:

<style>
html {
  scroll-padding-top: 80px;
}
</style>

Every anchor link on your site will now land 80px below the top of the viewport, clearing the nav perfectly. Let me explain why this problem exists, when this fix is not enough, and how to handle the edge cases.

Why This Bug Happens in the First Place

When you set a navbar to position: sticky with top: 0, the nav pins itself to the top of the viewport as you scroll. Visually it works perfectly. The problem shows up when someone clicks an anchor link, like a table of contents link or a "Jump to pricing" button.

The browser handles anchor link scrolling with a simple rule: scroll the target element to the top of the viewport. It does not check whether anything is stuck to the top. It does not know your nav exists. It scrolls the heading or section to y: 0, and your 80px nav sits right on top of it.

This is not a Webflow bug. It is how browsers handle position: sticky combined with anchor navigation. It happens in hand-coded sites too. The difference is that hand-coded sites usually catch it during development, while Webflow builders often discover it right before launch because the Webflow Designer preview does not always expose the issue clearly.

I have seen this bug go live on agency client sites more times than I can count. It is one of the first things I check when I audit a Webflow project through my white-label service.

The scroll-padding-top Fix Explained

The CSS property scroll-padding-top tells the browser: "When you scroll to an anchor target, leave this much space at the top." It was designed specifically for this use case.

html {
  scroll-padding-top: 80px;
}

Why apply it to html instead of body? Because scroll-padding is a property of the scroll container, and in most cases, the html element is the scroll container for the main page. Applying it to body works in some browsers but not all. The html element is the safe choice.

The value should be your nav's height plus a small buffer. If your nav is 72px, use 80px. If it is 60px, use 68px. That extra 8px gives the heading some breathing room instead of sitting flush against the bottom of the nav.

This property has excellent browser support. It works in Chrome, Firefox, Safari, and Edge. According to Can I Use, it has been supported in all major browsers since 2019. You do not need a fallback.

When to Use scroll-margin-top Instead

There is a sibling property called scroll-margin-top that works on the target element rather than the scroll container. Instead of telling the page "always leave 80px at the top when scrolling," you tell a specific element "when I am the scroll target, offset me by 80px."

.section-target {
  scroll-margin-top: 80px;
}

When would you use this instead of scroll-padding-top? Two scenarios:

  1. Different sections need different offsets. Maybe your pricing section has a sub-header that needs more clearance, or a section near the bottom of the page needs less because the nav is hidden on scroll by that point.
  2. You have multiple scroll containers. If you have a side panel or modal with its own scroll behavior, scroll-margin-top on individual elements gives you precise control without affecting other scroll contexts.

For most Webflow sites, scroll-padding-top on html is the right call. It is one line, it covers every anchor link on the page, and you do not have to remember to add a class to every scroll target.

In Webflow, you can apply scroll-margin-top through the custom properties panel or by adding a global style rule in your custom code. I prefer the custom code approach because it is easier to maintain and does not clutter the Webflow class system.

Handle Different Nav Heights on Mobile

Here is where a lot of tutorials stop, and where real projects start to get tricky.

Your desktop nav might be 80px tall. Your mobile nav might be 60px. If you hardcode scroll-padding-top: 80px, your mobile anchor links will overshoot by 20px, leaving an awkward gap between the nav and the heading.

Fix this with a media query:

html {
  scroll-padding-top: 88px;
}

@media (max-width: 767px) {
  html {
    scroll-padding-top: 68px;
  }
}

Match the breakpoints to your Webflow responsive settings. Webflow's tablet breakpoint is 991px and mobile landscape is 767px. If your nav height changes at the tablet breakpoint, add that media query too:

html {
  scroll-padding-top: 88px;
}

@media (max-width: 991px) {
  html {
    scroll-padding-top: 76px;
  }
}

@media (max-width: 767px) {
  html {
    scroll-padding-top: 68px;
  }
}

Measure your actual nav heights at each breakpoint. Do not guess. Open the published site, open DevTools, and check the computed height of the nav at each viewport width. Then add your 8px buffer and plug in the values.

Want a website that turns visitors into customers, not just compliments?

Book a 15-min intro

If you followed my guide on building a sticky shrinking navbar, you have a nav that starts at one height and animates to a shorter height on scroll.

Which height do you use for scroll-padding-top? The answer depends on user behavior.

When someone clicks an anchor link, they have usually already scrolled at least a little. That means your nav is likely in its shrunk state. So use the shrunk nav height for your scroll-padding-top value.

If your nav shrinks from 96px to 56px, set:

html {
  scroll-padding-top: 64px; /* 56px shrunk height + 8px buffer */
}

There is one edge case: if the user clicks an anchor link from the very top of the page before scrolling, the nav is still in its expanded state. The heading will land slightly behind the expanded nav. In practice, this is rare enough that I do not worry about it. The user scrolled to reach the link in the table of contents or the nav, so the nav is already shrunk.

If this edge case bothers you, the JavaScript approach handles it cleanly. But for 95% of real-world sites, the CSS fix with the shrunk height is perfect.

The JavaScript Approach for Dynamic Nav Heights

Sometimes the nav height is genuinely unpredictable. A common case is a notification banner that sits above the nav and can be dismissed. Before dismissal, the total sticky block might be 120px. After dismissal, it is 60px. No single CSS value can handle both states.

For this scenario, use JavaScript:

<script>
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
  anchor.addEventListener('click', function (e) {
    e.preventDefault();
    const target = document.querySelector(this.getAttribute('href'));
    const nav = document.querySelector('[data-nav="main"]');
    const navHeight = nav.offsetHeight;
    const targetPosition = target.getBoundingClientRect().top + window.pageYOffset - navHeight - 8;
    window.scrollTo({
      top: targetPosition,
      behavior: 'smooth'
    });
  });
});
</script>

This script intercepts every anchor link click, calculates the nav's current height at that moment, and scrolls to the target minus that height. The -8 adds the buffer.

Place this in the before-body custom code section so it runs after the DOM is ready.

I want to be clear: this JavaScript approach is the backup plan, not the default. CSS scroll-padding-top is simpler, more performant, and does not depend on JavaScript loading. Only reach for JavaScript when the nav height genuinely changes at runtime.

This sounds obvious, but I am spelling it out because I have caught this issue on sites that were already live.

After applying your fix, test every single anchor link on every page. This includes:

  • Table of contents links in blog posts
  • "Jump to section" buttons on landing pages
  • Nav links that point to sections on the same page (common on single-page sites)
  • Footer links that jump to a contact form or FAQ section
  • Any CTA button that scrolls to a form lower on the page

Test at every breakpoint. Desktop, tablet, mobile portrait. The offset that works at 1440px might not work at 375px if your nav height changes.

Test with smooth scrolling on and off. Some users disable smooth scrolling in their OS or browser settings. The offset should work either way, and with scroll-padding-top, it does. With JavaScript-based solutions, you need to make sure the scroll calculation works for both instant and smooth scrolling.

And test with the browser back button. If a user clicks an anchor link, scrolls around, and then hits the back button, the browser should return to the original scroll position. CSS scroll-padding-top preserves this behavior. Some JavaScript solutions break it by preventing the default anchor behavior.

Other Navigation Patterns That Cause the Same Problem

This is not just a sticky nav issue. Any element that overlays the top of the viewport can cause anchor links to land behind it.

  • Fixed headers. Same problem, same fix. Position: fixed behaves like position: sticky for this purpose.
  • Announcement bars. If you have a sticky banner above the nav, your scroll-padding-top needs to account for both the banner and the nav height combined.
  • Sticky sub-navigation. Some sites have a secondary nav that appears below the main nav when you scroll to a certain section. If both are sticky, add their heights together.

In every case, the fix is the same: measure the total height of all elements stuck to the top, add your buffer, and set that as your scroll-padding-top.

How This Affects SEO and AI Citations

Here is something most developers do not think about. Google and AI engines both value well-structured in-page navigation. A table of contents with working anchor links signals clear content organization. But if your anchor links scroll to the wrong position, users bounce back to the top and try again. That frustrated behavior sends negative engagement signals.

A dynamic table of contents with properly offset anchor links keeps users on the page and in the content. It is a small technical fix with an outsized impact on user experience and, indirectly, on how search engines evaluate your page quality.

The Bottom Line

Anchor links hiding behind sticky navs is a CSS problem with a CSS solution. Add scroll-padding-top to your html element, set the value to your nav height plus a buffer, and add media queries for responsive nav heights.

Do not overthink this. Do not add JavaScript unless your nav height changes at runtime. And test every anchor link at every breakpoint before you hit publish.

If you are an agency shipping Webflow sites and want someone to catch issues like this before your clients do, check out my white-label Webflow development service. I handle the build so you can focus on design and strategy.

Prefer to hire through Upwork?
Top Rated Plus, 100% Job Success, 450+ projects shipped. See the reviews and start a contract.
Hire me on Upwork

FAQ

Why do anchor links scroll behind my sticky navbar in Webflow?

The browser scrolls the target element to the very top of the viewport. A sticky nav sits on top of that position, hiding the content. The browser does not account for the nav height automatically.

What is the best CSS fix for anchor links behind a sticky nav?

Add scroll-padding-top to the html element with a value equal to your nav height plus a small buffer. This adjusts the scroll target for all anchor links site-wide.

Do I need JavaScript to fix anchor link offset in Webflow?

Only if your nav height changes dynamically, such as when a dismissible banner sits above it. For fixed-height navs, CSS is simpler and more performant.

All posts
the next step is small

Want a site that does this for you?

15 minutes, no deck, no pressure. Worst case, you leave with a free plan.

keep reading

More notes