Simple CSS solution to fixed header blocking anchor links

If you have ever worked on a site with a fixed navigation menu header that is “sticky” as you scroll, chances are you’ve run into this issue. When you click on a link to an anchor further down the page, it scrolls so the anchor is now at the very top of your window…behind the nav header that, of course, has remained at the top of the screen.

This is a common issue that you often see unaddressed even on some very popular websites. Luckily, the solution is fairly simple and can be done entirely with CSS – no jQuery or any kind of JavaScript needed. (Although you can use a JS solution, I generally prefer to use CSS for things like this when possible, as it is tends to be an easier and more lightweight solution.)

One last note before I get into it – I am assuming that you use the id rather than name attribute for your anchors. I might cover the reasoning behind this preference in another post.

There are a couple of viable solutions here – one that you will commonly see on forums, and one that is less frequently suggested but which I prefer:

Solution 1: hidden and negative margin

This solution is all over Stack Overflow and various other search results when you Google this problem. Let’s assume for simplicity that your nav header height is 100 pixels. Then it goes something like this:

.anchor {
    display: block;
    height: 100px;
    margin-top: -100px;
    visibility: hidden;
}

Once that is in your CSS, you would use it by placing an element with that anchor class right before the element that contains your id. For instance:

<p><a href="#jumplink">This links down to the h2 further down</a></p>
<div id="jumplink" class="anchor"></div>
<h2>This is what that link from the paragraph will jump down to</h2>

Note that the opening <div> tag’s class=”anchor” attribute is the piece that makes use of the .anchor definition from earlier. This div is invisible; it occupies 100px of height but is offset by the -100px of margin so it doesn’t take up “physical” space. It does its job in offsetting the fixed header.

So why don’t I like this solution? In short, the fact that it forces you to use an empty, hidden element with each anchor (the div in the above example). And you must have that empty element above whatever you are actually trying to get people to jump to. Imagine that instead of using the empty div, you just put the id and class=”anchor” attributes into the opening <h2> tag instead:

<p><a href="#jumplink">This links down to the h2 further down</a></p>
<h2 id="jumplink" class="anchor">This is what that link from the paragraph will jump down to</h2>

Clicking on the jump link would still take you to the correct anchor location with the view unobstructed by the fixed header, but your h2 text would be invisible. This is because of the display: hidden; line in the .anchor definition. It means that no matter what element you put that class=”anchor” attribute in (could be a div, span, paragraph, etc.), it will be invisible.

Because of this, you have to place the invisible element directly above whatever content it is you are actually trying to jump to, which is unideal. Having tons of invisible empty elements in your code is not only bad practice, but it is also mildly inconvenient. And what is worse in life than mild inconveniences?

Whew. Okay. Hopefully that made sense. And if not, feel free to try it out by opening Chrome Developer Tools (or your browser’s equivalent) and editing this page’s .anchor styling to the CSS snippet above and observe what happens to the h2s on the page. Now let’s move on to a simpler and—in my opnion—better solution.

Solution 2: opposite padding and margin

One thing I really like about using the id attribute is you can put it within pretty much any element to create an anchor. In this post, I’ve done so within the opening tags of the h2s above. Like the first solution, this one also involves creating an “anchor” class but rather than forcing us to use empty elements, we can continue to conveniently place ids within opening tags. Here is what to put into the stylesheet:

.anchor {
    padding-top: 100px;
    margin-top: -100px;
}

With this in the stylesheet (which it is for this site), this will work:

<p><a href="#jumplink">This links down to the h2 further down</a></p>
<h2 id="jumplink" class="anchor">This is what that link from the paragraph will jump down to</h2>

If you examine the anchor links and h2 subheadings on this page, you will see that is in fact exactly how I set it up.

What if my mobile and desktop headers are different sizes?

Let’s assume the sticky header is a different height on mobile – which for this site, is actually true. The mobile header is 80 pixels high as opposed to 100 pixels on desktop. We can use a simple CSS media query to redefine the anchor class specifically for screens under a certain number of pixels wide:

@media only screen and (max-width: 768px) {
    .anchor {
        padding-top: 80px;
        margin-top: -80px;
    }
}

Of course, you could argue that if we were truly coding for mobile first, we should have defined the class for mobile…first, and then wrote a media query using min-width for larger viewports. In either case, it is a relatively simple solution that works.

I say all of this with the disclaimer that my CSS knowledge is not so great (yet); at the moment, I am nowhere near as comfortable with CSS as I am with HTML. In all likeliness there is another solution that is even better – if you know of such a solution, please feel free to share in the comments.

A final touch: smooth scrolling

While we’re on the topic, I might as well briefly mention smooth scrolling. Regardless of how you solved for anchors being blocked by fixed headers, you might have also noticed that there is a rather abrupt jump animation when you click on an anchor link. You also might not have noticed that. It just depends on what kind of scroll animation the website is using, and as it happens, many sites have the abrupt teleporting animation by default. We can easily replace this with a smooth scrolling animation—again one of those things you can solve with JavaScript but is even easier with CSS:

html {
    scroll-behavior: smooth;
}

Yep, it’s that simple. All it takes is that one line in your CSS.

3 Responses

  1. Y. Acosta says:

    Thank you!!!! I’ve been looking FOREVER for a plugin-free solution for this that actually works! Great job!

  2. zv says:

    FYI: Solution 2 does not work in Chrome (at least in my case).

  3. Jake says:

    If the element would otherwise have a top margin or padding, you’ll need to account for that. Also you’ll need to account for margin-collapsing if the element above has a margin.

Leave a Reply

Your email address will not be published. Required fields are marked *