Question
How to Offset HTML Anchors for a Fixed Header Using CSS and JavaScript
Question
I have a fixed header at the top of my page. When I click a link to an anchor elsewhere on the same page, the browser scrolls that anchor to the very top of the viewport. Because the header is fixed, it covers the top part of the target content.
How can I offset the anchor position by about 25px, which is the height of the header, so the content remains visible after the jump?
I would prefer a solution using HTML or CSS, but a JavaScript solution would also be acceptable.
Short Answer
By the end of this page, you will understand why fixed headers overlap in-page anchors, how modern CSS solves it with scroll-margin-top, and what fallback techniques can be used when you need broader support or more control.
Concept
When you click an in-page anchor link such as #section2, the browser scrolls the target element into view. By default, it tries to align that target near the top of the viewport.
This becomes a problem when your layout includes a fixed header. A fixed header stays visible at the top of the screen, so the browser may place the target under that header instead of below it.
This is not really a bug in anchors. It is a layout issue caused by combining:
- in-page navigation
- fixed positioning
- automatic browser scrolling
Why this matters in real projects:
- Documentation pages often use anchor links.
- Landing pages use navigation links like
#featuresor#pricing. - Dashboards and admin panels often have sticky or fixed top bars.
- Accessibility and usability improve when the correct content is visible after navigation.
The most modern solution is CSS:
.target {
scroll-margin-top: 25px;
}
This tells the browser to leave extra space above the element when it is scrolled into view.
Older techniques include:
- adding top padding and negative margin
- using a
::beforepseudo-element as invisible spacing - JavaScript scrolling with a custom offset
In most modern codebases, CSS-first is the best approach because it is simpler, cleaner, and easier to maintain.
Mental Model
Imagine the browser is trying to place a bookmark exactly at the top edge of a window.
Now imagine you taped a solid bar across the top of that window. The bookmark is technically at the top, but you cannot see it because the bar covers it.
Offsetting an anchor means telling the browser:
"Scroll to this section, but stop a little earlier so it appears just below the fixed header."
So instead of aiming for the absolute top, you leave a small safety gap equal to the header height.
Syntax and Examples
Modern CSS solution: scroll-margin-top
If the element being linked to is the anchor target, give it a top scroll margin.
<nav>
<a href="#about">About</a>
<a href="#contact">Contact</a>
</nav>
<header class="site-header">My Header</header>
<section id="about" class="anchor-target">About section</section>
<section id="contact" class="anchor-target">Contact section</section>
.site-header {
position: fixed;
top: 0;
left: ;
: ;
: ;
}
{
: ;
}
Step by Step Execution
Consider this example:
<a href="#section1">Go to Section 1</a>
<section id="section1" class="anchor-target">Section 1</section>
.anchor-target {
scroll-margin-top: 25px;
}
Here is what happens step by step:
- The user clicks the link
Go to Section 1. - The browser reads
href="#section1". - It looks for an element with
id="section1". - It prepares to scroll that element into view.
- Because the target has
scroll-margin-top: 25px, the browser leaves25pxof space above it. - The section appears slightly below the top of the viewport.
- The fixed header no longer hides the section title or content.
JavaScript trace example
const target = .();
offset = ;
top = target.(). + . - offset;
.({ top });
Real World Use Cases
Common places this problem appears
- Single-page websites where navbar links jump to sections like About, Services, and Contact.
- Documentation sites with a table of contents linking to headings.
- FAQ pages where users jump directly to a question.
- Admin dashboards with sticky top navigation.
- Blog posts that link to headings inside the article.
Example scenarios
Product landing page
A fixed navigation bar links to #features, #testimonials, and #pricing. Without offsetting, users click a link and land on a heading that is partially hidden.
API documentation
A sidebar links to methods and endpoints. Developers need the heading and example code to be visible immediately.
Long settings page
A menu jumps to #security, #notifications, and #billing. Proper anchor offsets make navigation feel polished and predictable.
Real Codebase Usage
In real projects, developers usually solve this in one of these ways:
1. Set a shared header height variable
:root {
--header-height: 64px;
}
.section-anchor {
scroll-margin-top: var(--header-height);
}
This avoids hardcoding the same number in multiple places.
2. Apply the offset only to navigable sections
Instead of applying it to every element, teams often add a class only to headings or sections that can be linked directly.
<h2 id="installation" class="section-anchor">Installation</h2>
<h2 id="usage" class="section-anchor">Usage</h2>
3. Combine with smooth scrolling
html {
scroll-behavior: smooth;
}
This improves the user experience for anchor jumps.
4. Use JavaScript when the header height changes dynamically
For example:
Common Mistakes
1. Offsetting the wrong element
Beginners sometimes style the link instead of the target.
Broken
a {
scroll-margin-top: 25px;
}
This usually does not help, because the browser scrolls to the element with the matching id, not the clicked link.
Correct
#contact {
scroll-margin-top: 25px;
}
2. Forgetting that the header height may change
If your header is 25px on desktop but 60px on mobile, a fixed value may be wrong on some screens.
Better
:root {
--header-height: 60px;
}
@media (min-width: 768px) {
:root {
--header-height: 25px;
}
}
3. Using extra empty elements unnecessarily
Older code often adds empty anchor tags only for positioning. This can clutter HTML.
Comparisons
| Approach | Best for | Pros | Cons |
|---|---|---|---|
scroll-margin-top | Modern browsers and clean CSS solutions | Simple, readable, no extra markup | May not match legacy support requirements |
::before offset hack | Older CSS-based projects | No JavaScript required, works in many cases | Less intuitive, more of a workaround |
| Padding + negative margin | Legacy layouts | Can work without JS | Can affect layout and be harder to maintain |
JavaScript scrollTo | Dynamic headers or custom behavior | Fully controllable, supports smooth scrolling logic | More code, more maintenance |
scroll-margin-top vs JavaScript
Cheat Sheet
/* Best modern approach */
.target {
scroll-margin-top: 25px;
}
/* Reusable variable */
:root {
--header-height: 25px;
}
.target {
scroll-margin-top: var(--header-height);
}
/* Smooth scrolling */
html {
scroll-behavior: smooth;
}
/* Older fallback */
.target::before {
content: "";
display: block;
height: 25px;
margin-top: -25px;
}
// JavaScript offset scroll
const target = document.querySelector('#section');
const offset = 25;
const top = target.getBoundingClientRect().top + window.pageYOffset - offset;
window.scrollTo({ top, behavior: });
FAQ
Why do fixed headers overlap anchor links?
Because the browser scrolls the target to the top of the viewport, and the fixed header sits on top of that area.
What is the best CSS way to offset an anchor?
Use scroll-margin-top on the target element. It is the cleanest modern solution.
Can I solve fixed header anchor issues without JavaScript?
Yes. In most modern projects, CSS alone is enough using scroll-margin-top or an older pseudo-element fallback.
Should I put the id on an empty anchor tag or on the heading itself?
Usually on the heading or section itself. It keeps the HTML cleaner and more semantic.
What if my header height changes on mobile?
Use a CSS variable and update it in media queries, or use JavaScript if the height changes dynamically at runtime.
Does scroll-behavior: smooth fix the overlap problem?
No. It only changes the animation of scrolling. You still need an offset solution.
When should I use JavaScript for anchor offsets?
Use it when the header height is dynamic or when you need custom scrolling behavior beyond what CSS provides.
Mini Project
Description
Build a simple one-page site with a fixed header and navigation links that jump to sections such as About, Services, and Contact. The project demonstrates how to prevent anchored sections from being hidden behind the fixed header.
Goal
Create in-page navigation that scrolls to each section while keeping the section heading visible below a fixed header.
Requirements
- Create a fixed header at the top of the page.
- Add at least three navigation links that point to section IDs.
- Make each section target visible below the header after navigation.
- Use a CSS-first solution.
- Add smooth scrolling for a better user experience.
Keep learning
Related questions
Can You Style Half a Character in CSS? Text Effects with CSS and JavaScript
Learn how to style half of a character using CSS and JavaScript, including overlay techniques for dynamic text effects.
Check If a Checkbox Is Checked with jQuery
Learn how to check whether a checkbox is checked in jQuery using the correct selector, with examples, mistakes, and practical patterns.
Convert HTML and CSS to PDF in PHP: Options, Limits, and Practical Approaches
Learn how HTML-to-PDF conversion works in PHP, why CSS support varies, and how to choose a practical approach for Linux web servers.