How to add an automatic offset to scroll position for hash-links

April 14th, 20242 mins read

How to add an automatic offset to scroll position for hash-links

It's the subtle details that often make the most significant impact on user experience. One such detail that can frustrate users is when hash-links navigate to sections of a webpage, only to have those sections covered by a fixed navbar.

This issue arises because, by default, hash links scroll to positions that touch the browser window, disregarding fixed elements like navbars.

Imagine you're browsing a website with a fixed navbar, and a sidebar table of contents that offers easy navigation to different sections of a long-form article or a blog.

You click on one of the links in the table of contents, expecting to scroll to the corresponding section. Instead, you find yourself staring at the top of that section, hidden beneath the navbar.

The good news is that there's a simple solution to this problem, one that doesn't require diving into complex JavaScript code. Instead, a few lines of CSS can do the trick.

Understanding the issue

When a user clicks on a hash-link (e.g., #section2) within a page, the browser attempts to navigate to the corresponding element with the same ID (<div id="section2">).

However, if there are fixed headers, footers, or other elements that occupy space at the top of the viewport, the target element may end up hidden beneath them.

Solution: Use scroll-margin-top

As its name implies, this property bestows upon elements a top margin following a scroll event, effectively creating space between the top of the viewport and the targeted element.

h2 {
	scroll-margin-top: 50px; /* Adjust the value as needed */
}

/* For older versions of Safari (11 or older) */
@supports not (scroll-margin-top: 0) {
	h2 {
		scroll-snap-margin-top: 50px; /* Compatibility fallback */
	}
}

With this snippet, we ensure that when a user clicks on a hash-link, the browser gracefully scrolls to the designated section, leaving a comfortable buffer of 50 pixels between the section and the top of the viewport.

However, a word of caution: if you find yourself on Safari 11 or older (macOS or iOS), clicking anchor links may yield no response. Fear not. Incorporate the scroll-snap-margin-top property alongside scroll-margin-top.

Conclusion

While other methods may offer solutions dating back to the dawn of web development, the introduction of scroll-margin-top heralds a new era.


Intersted in seeing some videos? Check out my recent uploads.

What Are MCPs? I Used Cursor to Create a Repo and Push Code to GitHub
25:55

October 8th, 2025

What Are MCPs? I Used Cursor to Create a Repo and Push Code to GitHub

Understanding Docker Multi-Stage Builds (with Example) - Fast and Efficient Dockerfiles
21:26

September 1st, 2025

Understanding Docker Multi-Stage Builds (with Example) - Fast and Efficient Dockerfiles

3 Tips to Optimize Docker Images | Reduce Size, Improve Performance & Security
19:04

August 22nd, 2025

3 Tips to Optimize Docker Images | Reduce Size, Improve Performance & Security

Passwordless Auth in Node.js with Magic Links (Step-by-Step)
26:47

July 7th, 2025

Passwordless Auth in Node.js with Magic Links (Step-by-Step)

One Security Check Most Devs Forget in Their Signup Flow
10:11

May 20th, 2025

One Security Check Most Devs Forget in Their Signup Flow

How to Implement OAuth in Your Node.js Backend (GitHub & Google Login)
30:03

May 15th, 2025

How to Implement OAuth in Your Node.js Backend (GitHub & Google Login)

Next.js JWT Auth Explained: Cookies, Refresh Tokens & 2FA (No Auth.js)
01:02:51

May 6th, 2025

Next.js JWT Auth Explained: Cookies, Refresh Tokens & 2FA (No Auth.js)

Add 2FA to Your Node.js App with otplib
44:19

April 6th, 2025

Add 2FA to Your Node.js App with otplib

How to Run PostgreSQL with Docker Locally
25:48

March 31st, 2025

How to Run PostgreSQL with Docker Locally

Node.js Auth API with JWT, PostgreSQL & Prisma | Vibe Coding
01:23:24

March 30th, 2025

Node.js Auth API with JWT, PostgreSQL & Prisma | Vibe Coding

Create Stunning Emails in React — React-Email & Resend
33:53

March 6th, 2025

Create Stunning Emails in React — React-Email & Resend

Complete Guide — React Internationalization (i18n) with i18next
56:15

February 17th, 2025

Complete Guide — React Internationalization (i18n) with i18next

Build an Animated FAQ Accordion Component in React and Tailwind CSS
30:25

February 8th, 2025

Build an Animated FAQ Accordion Component in React and Tailwind CSS

Learn Shell Scripting With Me Using ChatGPT
47:12

January 30th, 2025

Learn Shell Scripting With Me Using ChatGPT

How to Build a Mega Menu in Next.js – Step-by-Step Tutorial
36:33

January 20th, 2025

How to Build a Mega Menu in Next.js – Step-by-Step Tutorial

The Easiest Way to Make Your Footer Stick to the Bottom || CSS Grid or Flexbox
11:00

January 13th, 2025

The Easiest Way to Make Your Footer Stick to the Bottom || CSS Grid or Flexbox

Form Validation in Next.js with React Hook Form & Zod
27:47

October 31st, 2024

Form Validation in Next.js with React Hook Form & Zod

Styling Active Links in Next.js
08:31

January 20th, 2024

Styling Active Links in Next.js

What is Bun — A JavaScript All-in-one Toolkit
29:01

January 17th, 2024

What is Bun — A JavaScript All-in-one Toolkit

Copy and Paste with JavaScript — 7 lines of code 🔥
01:00

December 27th, 2023

Copy and Paste with JavaScript — 7 lines of code 🔥

Migrate a Node.js Project to use Bun — 60 seconds ⚡️
01:00

December 25th, 2023

Migrate a Node.js Project to use Bun — 60 seconds ⚡️

Build a Slackbot With Slack API and Node.js
38:16

December 19th, 2023

Build a Slackbot With Slack API and Node.js

Technical Writing & Technical Blogging [w/ Asaolu Elijah]
17:58

December 30th, 2020

Technical Writing & Technical Blogging [w/ Asaolu Elijah]