Fixed Headers, On-Page Links, and Overlapping Content, Oh My!

Avatar of Chris Coyier
Chris Coyier on (Updated on )

Take the pain out of building site search with the Algolia hosted API. Start free now!

Let’s take a basic on-page link:

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

When clicked, the browser will scroll itself to the element with that ID: <section id="section-two"></section>. A browser feature as old as browsers themselves, just about.

But as soon as position: fixed; came into play, it became a bit of an issue. The browser will still jump to bring the newly targeted element into view, but that element may be obscured by a fixed position element, which is pretty bad UX.

I called this “headbutting the browser window” nearly 10 years ago, and went over some possible solutions. Nicolas Gallager documented five different techniques. I’m even using a fixed position header here in v17 of CSS-Tricks, and I don’t particularly love any of those techniques. I sort of punted on it and added top padding to all my <h3> elements, which is big enough for the header to fit there.

There is a new way though! Finally!

Šime Vidas documented this in Web Platform News. There are a bunch of CSS properties that go together as part of CSS scroll snapping, but it turns out that scroll-padding and scroll-margin can be used outside of a scroll snapping container.

/* This works in Chrome 73, but NOT FOR LONG. */
body {
  scroll-padding-top: 70px; /* height of sticky header */

/* Ultimately, this is the correct place for scroll-padding */
html {
  scroll-padding-top: 70px; /* height of sticky header */

This only works in Chromium browsers:

See the Pen
Scroll Padding on Fixed Postion Headers
by Chris Coyier (@chriscoyier)
on CodePen.

Hiroyuki Ikezoe wrote in to tell me that <body> is not the correct place to use scroll-padding, as document.scrollingElement is actually <html>. Unfortunately Chrome has it implemented (v73) such that it only works on the <body> right now, but there is a bug filed and the likely outcome is that it stops working on the <body> and only works on <html>. This is the same situation as the native specced custom scrollbars: they only work on <html>.

This is such a useful thing we should hoot and holler for WebKit and Firefox to do it.