Grow your CSS skills. Land your dream job.

Hash Tag Links That Don’t Headbutt The Browser Window

Published by Chris Coyier

When a link includes a hash, like this:

<a href="#section-two">Section Two</a>

The browser window will scroll itself (instantly) into such a position where the element with the ID of "section-two" is visible. It scrolls to the minimum possible position to make that element wholly visible. This is typically a matter of scrolling the window down, but do note that if any scrollable parent container were to require horizontal scrolling to make the element visible, the browser will do that as well. I think of this as "headbutting" the browser as the element is flush with the top edge of the browser window.

This can be:

  • Possibly aesthetically un-pleasing
  • Possibly confusing (especially when you jump into a area with loads of other headers)
  • In the case of a fixed-position stay-on-top header, hugely problematic

The fixed position header thing is the biggest threat, so let's use that as an example and fix it.

Rock Solid (Dirty HTML) Method

Instead of focusing on the most progressive way to handle it first like we usually do, let's look at the most cross-browser compatible possible way to get it done.

Instead of putting the ID on the header, we'll put it on an empty span tag within the header. This won't affect the appearance of the header at all. However, using a span for a purely behavior thing like this isn't ideal.

<a href="#goto">Jump</a>

<!-- yadda yadda yadda -->

<h2>
   <span id="goto"> &nbsp; </span>
   Header
</h2>

Then in the CSS, we'll suck up the span north of the actual header with negative top margin. Then we'll push the header back down via positive bottom padding, mitigating any weird layout problems that the suck-up will cause.

h2 span { 
  margin-top: -300px;        /* Size of fixed header */
  padding-bottom: 300px; 
  display: block; 
}

Ideally, we would just absolutely position the span on top of the header, but IE7 doesn't play nice with that, ignoring the jumps all together. IE6 has major issues with fixed positioning, so this demo is borked in that, and let's not go there, although I'm sure this idea basically works in it if you can shim the fixed position issue.

View Demo

Fancier (Clean HTML) Method

Using the extra span is non-semantic for two reasons: 1) You are associating the link directly to an empty span, which is meaningless. 2) The span shouldn't be in there at all. The HTML should be:

<a href="#goto">Jump</a>

<!-- yadda yadda yadda -->

<h2 id="goto">Header</h2>

Then to solve the headbutting/padding issue, we'll use a pseudo element to do the same task that the span was doing in our dirty HTML version. We'll give it a height, which pushes up the size of the header, then use negative margin to yank it back up into place.

h2:before { 
  display: block; 
  content: " "; 
  margin-top: -285px; 
  height: 285px; 
  visibility: hidden; 
}

View Demo

More from Nicolas Gallagher

I posted the original idea for this over on Forrst, and Nicolas Gallagher picked up on it and ran with it, as Nic likes to do =). He points out that the height/margin technique can be a problem if you have a background on the headers and don't want that to expand. He prevents that by experimenting with background-clip, using a under-border, and others. And big props to Ira McMahon for spurring the idea on Forrst.

View Demo

As part of Nic's demos, he uses :target to change the color of the header after "the jump". This is a great reminder of that pseudo selector and a perfect use for it. Target will match when the hash tag in the URL matches the ID of an element. Quick reminder: if the URL is http://blahblahblah.com/#header-one and there is an element like <h2 id="header-one">Whatup</h2> then this selector will match h2:target { background: yellow; }.

More from Patrick Strietzel

I discovered that IE7's hash tag behaviour (ignoring the padding-top) can be tricked by setting the display value to "inline-block".

h2 { 
  margin-top: -285px; 
  padding-top: 285px; 
  display: inline-block;
}

Of course, a display change like that can have consequences. Inline-block is very different from block, so beware.

More from Kirk Gleffe

Kirk found a way to do it with just margin and a bit of transition-delay:

See the Pen Hash Links with Margin by KC (@kcgleffe) on CodePen.

Comments

  1. Permalink to comment#

    Wow – I’ve being doing web development since 1994 and somehow I never knew that you could point the hashed anchor to something other than a named anchor.

    I’ve always done:

    <a href="#somewhere" rel="nofollow">Go Somewhere</a>
    
    <a name="somewhere">Here</a>
    • gstoert
      Permalink to comment#

      well lance,
      then you actually didn’t do web development but web “design”, i think.
      and yes there is a difference and yes you have to know such facts as a “developer” ;)
      please don’t take it personally, but it’s just like that.

    • Permalink to comment#

      Actually, I do “development” (ie. data and algorithms) not design – hence my middling familiarity with the finer points of html.

  2. Anders
    Permalink to comment#

    You should also be able to pull this off without the need for any pseudo elements. I believe this should do the trick:

    h2 {margin-top: -285px; padding-top: 285px;}

    Not able to try it in IE7 & below atm. As long as you avoid having backgrounds/bg images on the headings, you should be ok.

  3. Jimmy
    Permalink to comment#

    @Lance
    Holy, Cow! I thought the exact same thing too! So I was a bit confused when reading this. I’m itching to go back to some old sites and redo this part now…

  4. tully
    Permalink to comment#

    For those of you that weren’t away that anchor tags can be linked to the id of any element may want to read http://www.w3.org/TR/html401/struct/links.html#h-12.2.3.

    And when it comes to making these types of links more visually appealling, I like to use Ariel Flesler’s jQuery.ScrollTo plugin http://flesler.blogspot.com/2007/10/jqueryscrollto.html. It’s been a round a long time and well done.

  5. Permalink to comment#

    I’ve always done this:

    <a name="someheading"></a>
    <h2>Some Heading</h2>

    I don’t think it’s any more un-semantic than your typical (non-contextual) hyperlink, and it completely sidesteps the css issues.

    • Permalink to comment#

      sorry, I meant to use id instead of name. And yes, there would be styling involved to give it height. What I wanted to “sidestep” was all the pulling-up-and-pushing-down.

      However, after looking at Chris’ demos, I think I missed the point of these exercises (I only looked at your demos, at first, and Chris’ has a giant red banner that really illustrates the complications these methods are trying to address).

      Typically, I have hash links where the webpage’s layout wouldn’t present such problems. An extra 2em of empty space doesn’t bother me (typically, a hash link would be between major sections, so extra space would be appropriate anyway). However, if I actually need to work around another element (like that giant banner, or a photo or comment box or something), then it wouldn’t be an appropriate solution.

  6. Permalink to comment#

    Wow, this is interesting. I just recently implemented this exact concept (using “dirty HTML” into this site which I coded for a designer friend.

    You’ll see it has the fixed-position header and it uses the jQuery scrollTo plugin. I had it working exactly the same in pretty much every browser with no extra HTML elements — but IE7 kept collapsing the margin when one of the links was clicked. I’ve seen this behaviour before with IE7, but nothing seemed to fix it, even giving the element layout and whatnot. I even tried to reapply the margin settings via JavaScript. (For IE6 the header isn’t fixed, it just scrolls normally)

    So I had to resort to adding the extra element to avoid using negative margins. I think the pseudo-element solution is a great option, but I’m wondering if the same problem would occur with the margins in IE7. Would be worth looking into.

  7. It is Good.

    I have always go with the below to target location.

    <a name=”top”>Anchor Position</a>
    <a href=”#top” >Top</a>

    -vara

  8. Permalink to comment#

    how about this

    <a href="#section">Jump</a>
    <h2 id="section" style="padding-top:50px">Header</h2>

  9. Nice examples of hash tag links for on-page scrolling. LT

  10. Paul Jorge
    Permalink to comment#

    I have a fixed position header 70px high. By playing with the values I was able to get this to work in Chrome. Is there a fix that works in IE 10? Here is my CSS snipet:

    h2
    {
    font-family:Georgia, “Times New Roman”, Times, serif;
    color:#306;
    }

    h2:before {
    content: ” “;
    margin-top: -175px;
    height: 175px;
    visibility: hidden;
    display: block;
    display: inline-block;
    }

  11. Bryan
    Permalink to comment#

    What about if the div you are trying to scroll to is nested in multiple other divs? For example using bootstrap with row and column classes and trying to scroll to a specific column of content.

    • Paul Jorge
      Permalink to comment#

      I’m having the same problem with dreamweaver tabbed panels.

  12. once again, you helped solve my problem quickly! thanks a ton.

  13. Alessio
    Permalink to comment#

    If have a background on my <div id="goto" class="section"></div> which fix can I use to avoid a huge space before the content of this div start (in a one page layout)?

  14. I was thinking the issue to solve needs some js. But you come up cleaner css h2:before method. Thank you so much for sharing.

  15. Reony Tonneyck
    Permalink to comment#

    How would I prevent The URL in the browser updating with the hashtag?

    I know there’s a way to do it with JavaScript or jQuery but I can’t find it. Thanks

  16. Might be usefull to add this to the Rock Solid (Dirty HTML) Method :
    h2 span {
    margin-top: -300px; /* Size of fixed header */
    padding-bottom: 300px;
    display: block;
    font-size:1px;
    line-height:1px;
    }

  17. David Tien
    Permalink to comment#

    I’m wondering if there’s a way to do this without having to hard code the pixels for height and margin-top? I used this in a project of mine and when you change the size of the browser window it doesn’t work properly.

Leave a Comment

Current day month ye@r *

*May or may not contain any actual "CSS" or "Tricks".