Fixed page header overlaps in-page anchors

235,092

Solution 1

I had the same problem. I solved it by adding a class to the anchor element with the topbar height as the padding-top value.

<h1><a class="anchor" name="barlink">Bar</a></h1>

I used this CSS:

.anchor { padding-top: 90px; }

Solution 2

If you can’t or don’t want to set a new class, add a fixed-height ::before pseudo-element to the :target pseudo-class in CSS:

:target::before {
  content: "";
  display: block;
  height: 60px; /* fixed header height*/
  margin: -60px 0 0; /* negative fixed header height */
}

Or scroll the page relative to :target with jQuery:

var offset = $(':target').offset();
var scrollto = offset.top - 60; // minus fixed header height
$('html, body').animate({scrollTop:scrollto}, 0);

Solution 3

html {
  scroll-padding-top: 70px; /* height of sticky header */
}

from: https://css-tricks.com/fixed-headers-on-page-links-and-overlapping-content-oh-my/

Solution 4

I use this approach:

/* add class="jumptarget" to all targets. */

.jumptarget::before {
  content:"";
  display:block;
  height:50px; /* fixed header height*/
  margin:-50px 0 0; /* negative fixed header height */
}

It adds an invisible element before each target. It works IE8+.

Here are more solutions: http://nicolasgallagher.com/jump-links-and-viewport-positioning/

Solution 5

From 4/2021 there is single non-hacky and fully cross-browser solution:

h1 {
  scroll-margin-top: 50px
}

It is part of CSS Scroll Snap spec. Runs on all modern browsers. (For more context, see the MDN pages about scroll-margin-top, scroll-padding-top)

Share:
235,092
Tomalak
Author by

Tomalak

I know a bit about SQL, Regular Expressions, XSLT, ColdFusion, JavaScript, Python, PowerShell and scripting languages in general. I also have a Unicorn. :) Further I have a Twitter account I update once in a while, but you won't read much technical stuff there.

Updated on July 29, 2022

Comments

  • Tomalak
    Tomalak almost 2 years

    If I have a non-scrolling header in an HTML page, fixed to the top, having a defined height:

    Is there a way to use the URL anchor (the #fragment part) to have the browser scroll to a certain point in the page, but still respect the height of the fixed element without the help of JavaScript?

    http://example.com/#bar
    
    WRONG (but the common behavior):         CORRECT:
    +---------------------------------+      +---------------------------------+
    | BAR///////////////////// header |      | //////////////////////// header |
    +---------------------------------+      +---------------------------------+
    | Here is the rest of the Text    |      | BAR                             |
    | ...                             |      |                                 |
    | ...                             |      | Here is the rest of the Text    |
    | ...                             |      | ...                             |
    +---------------------------------+      +---------------------------------+
    
  • vlad saling
    vlad saling over 12 years
    this solution works indeed, but it is not bulletproof and it requires lot of finetuning for different browsers :( good try thoug. I am searching for a solution without extra markup.
  • theorise
    theorise over 12 years
    Shame this is so messy, means you can't just use IDs. Would love to find a solution, I have tried all sorts of things.
  • Tomalak
    Tomalak about 12 years
    Mh. Not bad, but still quite a hack (and does not work in IE, sadly).
  • mutttenxd
    mutttenxd over 11 years
    @Tomalak Be aware that this solution makes links inside the padding area non-clickable. To fix this you must use z-index, and set the links value higher than the anchor's. Remember you topbar should have the highest z-index value, so the page's content don't float ontop of the topbar when you scroll.
  • Knickedi
    Knickedi over 10 years
    Did not work in Chrome v28 due to a WebKit fixed position bug causing header to disappear. This answer did the trick im my case.
  • zipzit
    zipzit about 10 years
    MuttenXd.. you are my hero. I had weird non-function link problem on my site (unrelated to anchors) been driving me crazy. Your non-clickable comment really helped. I owe ya big!
  • Jake Wilson
    Jake Wilson about 9 years
    Isn't adding padding to the top of the anchor going to push it farther down? Don't you want it farther up instead in order to account for the fixed header?
  • fechnert
    fechnert almost 9 years
    use <span> instead of <a> to avoid the colored headlines.
  • Skippy le Grand Gourou
    Skippy le Grand Gourou over 8 years
    As suggested in Roy Shoa's answer, add margin-top: -90px; to counter the gap created by padding.
  • jimmyplaysdrums
    jimmyplaysdrums over 8 years
    I used .anchor:target {padding-top: 90px;} so that it only added the padding when they used the anchor tag. This way there isn't always a bunch of padding if the page loads at the top. Only when it loads a that specific point lower on the page.
  • Jamie Carl
    Jamie Carl over 8 years
    This is the solution that worked perfectly for me without stuffing up anything else in my page layout.
  • Boguslaw
    Boguslaw over 8 years
    If you use anchor.js library, the css solution works perfectly.
  • Tomalak
    Tomalak about 8 years
    That's not hacky at all! Nice solution.
  • AJJ
    AJJ almost 8 years
    I have come to the same conclusion and solution. It seems like every body else is forgetting about external links that use the hashtags.
  • Admin
    Admin over 7 years
    <pre><code> // handle hashes when page loads // <stackoverflow.com/a/29853395> function adjustAnchor() { const $anchor = $(':target'); const fixedElementHeight = $('.navbar-fixed-top').outerHeight(); if ($anchor.length > 0) window.scrollTo(0, $anchor.offset().top - fixedElementHeight); } $(window).on('hashchange load', adjustAnchor); $('body').on('click', "a[href^='#']", function (ev) { if (window.location.hash === $(this).attr('href')) { ev.preventDefault(); adjustAnchor(); } });</pre></code>
  • Mert S. Kaplan
    Mert S. Kaplan over 7 years
    Not ".target", should be ":target"
  • Mert S. Kaplan
    Mert S. Kaplan over 7 years
    This solution has a problem when used with list in WebKit. Look: stackoverflow.com/questions/39547773/…
  • Mert S. Kaplan
    Mert S. Kaplan over 7 years
    CSS solution has a problem when used with list in WebKit. Look: stackoverflow.com/questions/39547773/…
  • webvitaly
    webvitaly over 7 years
    @MertS.Kaplan ".target" is the class 'target'.
  • scavenger
    scavenger over 7 years
    look at the link Badabam provided, there is a second solution, which I use and which works in Chrome and Firefox
  • Jeffrey Simon
    Jeffrey Simon about 7 years
    I was able to use a slightly modified version of this code from Thunder. I tried many of the CSS-only and simpler solutions on this page and on stackoverflow.com/questions/10732690/… and on stackoverflow.com/questions/10732690/… but they were not usable due to my requirements. The page is an FAQ page with many anchors to position to, both from on and off the page, so could have have lots of blank spots. Continued in next comment ....
  • Jeffrey Simon
    Jeffrey Simon about 7 years
    To make it work for me, here are the minor changes: (1) change "<=" in the href.index line to "<" to allow on-page navigation; (2) change "a.navigation" that had to be "#faq a" for my use; (3) change var fromTop = 250 to a ternary based on window.innerWidth for differences in mobile vs. desktop; (4) change 400 in the animate to 1, as the animation is not desired and 0 does not work; (5) add if ($('my-page-selector').length before the call to offSetScrollFromLocation in the anonymous function to prevent unnecessary calls on pages where not needed.
  • kwiat1990
    kwiat1990 about 7 years
    I have that problem today and I've used your solution. Although it seems that you don't need timeout. This works as IIFE as well.
  • Jpsy
    Jpsy about 7 years
    @kwiat1990: Yes, that may be possible – but only if your JS is placed at the very end of the HTML code. Otherwise your scroll target might not yet be in the DOM. Actually that is the whole point of using onDomReady. So I guess the timeout version is the stable way to go.
  • krulik
    krulik about 7 years
    It won't work if the target has top padding or border since it relies on margin-collapse.
  • Suresh Karia
    Suresh Karia almost 7 years
    Brilliant, Worked like a charm! thank you @Adripants , solved very old bug in using psuedo css without changing single dom element.
  • Endless
    Endless almost 7 years
    :target was just Brilliant!
  • Tomalak
    Tomalak almost 7 years
    Solves the problem, but not the "without the help of Javascript" part...
  • Jarod Thornton
    Jarod Thornton almost 7 years
    I used a little javascript to add and remove padding as needed. $(function(){ $('#jump').click(function() { $('#id').addClass("anchor"); }); $('.top').click(function() { $('#id').removeClass("anchor"); }); });
  • Giulio Caccin
    Giulio Caccin over 6 years
    OP asked not to use javascript.
  • Giulio Caccin
    Giulio Caccin over 6 years
    OP asked not to use javascript.
  • RachieVee
    RachieVee over 6 years
    Tested this on the latest Firefox (55.0.3 on PC) and it works now. :-)
  • Augusto Samamé Barrientos
    Augusto Samamé Barrientos over 6 years
    This answer was the only one that worked for external links. However it does not work for me when the link is called in the same page :( Any ideas?
  • snap
    snap over 6 years
    Works with external links but not with in-page links.
  • Jesse
    Jesse over 6 years
    @Augusto did you find a solution? I am having the same problem.
  • Augusto Samamé Barrientos
    Augusto Samamé Barrientos over 6 years
    Unfortunately, no :(
  • Avatar
    Avatar almost 6 years
    Only FYI: If the targeted anchor element has style display: table; then it will not work.
  • Tomalak
    Tomalak almost 6 years
    Thank you very much for sharing! Old as the thread is, there still does not seem to be one canonical solution to this problem.
  • twknab
    twknab almost 6 years
    +SteveCinq You read my mind -- I was facing the same issues where shared solutions were not working as predicted. Adding in the <span> with the anchor in the span, along with adding in the padding and margin worked for me. Thanks so much for taking the time to submit this answer despite the age of the thread!
  • Alireza
    Alireza over 5 years
    I loved :target
  • Tomalak
    Tomalak over 5 years
    Isn't that exactly the same method that Guillaume Le Mière and a few others have shown in this thread?
  • Matt Underwood
    Matt Underwood over 5 years
    It is very similar but I think it is easier to understand and a more detailed response with an even more in-depth link to an outside source.
  • Tomalak
    Tomalak over 5 years
    Pragmatic, but there is a downside - it only works for the first heading on a page. If there are more headings, you will start to see large gaps in the text because of the <br> tags before each heading.
  • Ali Kanat
    Ali Kanat about 5 years
    Please try to explain why you think this script would solve the problem
  • Brett
    Brett almost 5 years
    Finally - I was using external anchors and none of the other solutions were working and I couldn't figure out why when everyone else was saying they were working. I did notice with this that my experience with IE was that it required twice the amount of delay to work. Perhaps a more solid method would be to put a small delay and then run a loop that checks if the element exists and when it does execute the code; perhaps with minor delays between each check.
  • Vanessa King
    Vanessa King almost 5 years
    This is perfect—it worked without having to go back and add a class to the target ids. So simple and clean, thanks!
  • WoodrowShigeru
    WoodrowShigeru over 4 years
    Note that this won't work if the target element itself happens to have display: flex; or padding-top. Do use a dedicated anchor element (like <a name="my_link">{leave this empty}</a>) in those cases.
  • Chaya Cooper
    Chaya Cooper over 4 years
    Note: This only worked with my code once I added visibility: hidden; to the :target::before pseudo-element.
  • CraZ
    CraZ over 4 years
    The :target::before didn't work for us so I used h1[id]::before, h2[id]::before, ..., h6[id]::before instead (I needed to use it at headings only).
  • Flimm
    Flimm over 4 years
  • Flimm
    Flimm over 4 years
    What is the difference between scroll-padding-top and scroll-margin-top?
  • davidloper
    davidloper over 4 years
    Works for me. I am navigating to the anchor from another page. Others didn't work. Thanks!
  • Tomalak
    Tomalak over 4 years
    A few others have discovered this solution before you, see the the answers on page 1, for example stackoverflow.com/a/56467997/18771
  • jamawe
    jamawe over 4 years
    Sorry, haven't noticed! Thanks!
  • Juliusz Gonera
    Juliusz Gonera over 4 years
    This doesn't work for scrolling entire page in Edge and Safari (bugs.webkit.org/show_bug.cgi?id=179379).
  • Juliusz Gonera
    Juliusz Gonera over 4 years
    This doesn't work for scrolling entire page in Edge and Safari (bugs.webkit.org/show_bug.cgi?id=179379).
  • Jordy
    Jordy about 4 years
    This is not working when the target is within a flexbox for me.
  • Priya Payyavula
    Priya Payyavula about 4 years
    Savior! Simple,on point. None of the other solutions worked for me because I am using display:flex. Thanks a ton, it was so frustrating trying all the solutions.
  • ich5003
    ich5003 about 4 years
    Thank you very much, your CSS version was the only feasible solution for me, as I didn't want to use javascript and many other css solutions required changing the anchors which I wasn't able to, as they are generated by my CMS.
  • MacroMan
    MacroMan almost 4 years
    It is the same solution, but I can understand and follow this one easily. The others didn't give me enough information.
  • yehanny
    yehanny almost 4 years
    This is a BEAUTIFUL solution, I used scroll-behavior: smooth !important; also to go anchor, really simple in two lines of code
  • Tomalak
    Tomalak almost 4 years
    scroll-margin-top has been suggested earlier in this thread
  • Mu-Tsun Tsai
    Mu-Tsun Tsai almost 4 years
    In Safari it is called scroll-snap-margin-top: caniuse.com/#search=scroll-margin-top
  • Crystal Gardner
    Crystal Gardner almost 4 years
    Scroll margin is not applied for scrolls to fragment target...this will not work for anchor tags developer.mozilla.org/en-US/docs/Web/CSS/scroll-margin-top
  • thisismydesign
    thisismydesign almost 4 years
    For me this created a 60px-high <a> tag that was masking other links that stopped working as a result. Try this instead: stackoverflow.com/a/13184714/2771889
  • Odair Augusto Trujillo Orozco
    Odair Augusto Trujillo Orozco almost 4 years
    This one works for me, but not in Firefox for Linux version 78.
  • thiras
    thiras over 3 years
    Probably the best solution I've ever seen.
  • ujifgc
    ujifgc over 3 years
    Works perfectly. I have a large table and span#anchors on some cells. Have set td span[id] { scroll-margin-top: 80px; } and it works, scrolling exactly with this offset. Much faster rendering than pseudo-elements with margin and padding.
  • WoodrowShigeru
    WoodrowShigeru over 3 years
    @thisismydesign, The answer you're pointing to has the same issue that you're claiming it solves.
  • Perry
    Perry over 3 years
    This should be the accepted answer now. The market has caught up.
  • Pixelcode
    Pixelcode over 3 years
    Thanks for this amazing solution! I've always struggled with that problem until now when you – my new hero – reached my horizon. Thank you so much!
  • jamheadart
    jamheadart over 3 years
    I was going to add something similar after trying SO many different techniques, I ended up having the same idea. After the start of any div that should be the anchor, I just include a span element with an anchor class and the id for it. I actually used these properties: content: " "; display: block; position: relative; padding-top: 100px; margin-top: -100px; visibility: hidden; pointer-events: none;
  • Stéphan Champagne
    Stéphan Champagne over 3 years
    Excellent. Exactly what I was looking for. Credit is given to css-trick, NICE :)
  • dakur
    dakur over 3 years
    It is fixed in Safari TP now.
  • dakur
    dakur over 3 years
    It did not work in Safari for a long time, but it is fixed in TP now. See github.com/mdn/browser-compat-data/issues/…
  • user0474975
    user0474975 about 3 years
    Other solution I was using seemed to have stopped working in Chrome 89/Edge/IE. This solution fixed it.
  • user0474975
    user0474975 about 3 years
    I was using this solution for about a year and then it stopped working (Chrome 89, Edge) although it still works in Firefox.. The scroll-padding-top solution (below) does seem to work for Chrome, though.
  • mario
    mario about 3 years
    you saved my day, it works very smoothly. (tested on firefox)
  • dakur
    dakur almost 3 years
    Released in 14.1 so it is fully cross-browser now!
  • dakur
    dakur almost 3 years
    This was released in Safari 14.1 as well on 4/2021.
  • dakur
    dakur almost 3 years
    Note that in nowadays there is single no-hack fully cross-browser solution – scroll-margin / scroll-padding.
  • basZero
    basZero almost 3 years
    The :target solution does not work if the element is using display flex.
  • codycustard
    codycustard almost 3 years
    This should be the accepted answer, works in all but IE
  • Todd Gillette
    Todd Gillette almost 3 years
    scroll-margin-top is also an option, though I haven't figured out under what conditions, if any, it would be preferable to scroll-padding-top.
  • Andreas
    Andreas almost 3 years
    You might need to add also z-index: -1 or something similar to :targetin case the slution is overlapping something it should not.
  • Andreas
    Andreas almost 3 years
    ok, after reading all the other answers and concidering, that we have now 2021, I made it work perfectly with :target { scroll-margin-top: 60px; } (kind of the best from both worlds)
  • Andreas
    Andreas almost 3 years
    that's my persona favorite: :target { scroll-margin-top: 2em; }
  • Puddle
    Puddle over 2 years
    this just creates a huge space of padding above the the targeted section which you easily notice when scrolling back up. very hacky solution
  • Mikko Rantalainen
    Mikko Rantalainen over 2 years
    I'd recommend using selector :target instead of given element names.
  • Lideln Kyoku
    Lideln Kyoku over 2 years
    Unfortunately this prevents clicking on elements that are above (before) the target element as they are overlapped by the pseudo-element. People should use scroll-padding-top unless they need IE11
  • Lideln Kyoku
    Lideln Kyoku over 2 years
    Works for me for all browsers except IE11. At least, now I can once again click on the links that are above/before this element.
  • Lideln Kyoku
    Lideln Kyoku over 2 years
    @MikkoRantalainen what's the relation with the current answer? @Flimm it seems that using scroll-padding-top is preferrable in most cases. scroll-margin-top allows for more flexibility in some scenarii only.
  • Lideln Kyoku
    Lideln Kyoku over 2 years
    It prevents clicks on links/elements that are above/before the target element. It may have been the least of two evils earlier, but nowadays people should only use scroll-padding-top.
  • Mikko Rantalainen
    Mikko Rantalainen over 2 years
    If you have fixed or sticky page header, using :target selector fixes all fragment anchor usage instead of only selected elements (and leaving other targets incorrect). Using :target instead of * as the selector avoids applying this property to every element in the DOM.
  • Tomalak
    Tomalak over 2 years
    That's pretty useful!
  • MarsAndBack
    MarsAndBack over 2 years
    Very elegant; no messing with the layout. Works for me! I would still add JS to update the height value dynamically.
  • binglong li
    binglong li about 2 years
    This is a better answer.stackoverflow.com/questions/10732690/…. For anchor on the middle of content,When there is no content below and window can not be scroll, The :target::before will suddenly add some empty block.
  • basZero
    basZero about 2 years
    Does not work in my case (using Bootstrap 4.5 row with an anchor)