How to Use CSS scroll-margin for Anchor Links
The CSS scroll-margin property is a modern styling
feature that establishes an offset around an element when it is scrolled
into view via anchor links, keyboard navigation, or JavaScript APIs.
When websites use fixed or sticky navigation bars, jumping to an anchor
link often causes the target element to become obscured behind the
header. This article explores how scroll-margin resolves
this common issue cleanly by defining invisible breathing room around
targeted elements without altering page layout, eliminating the need for
legacy hacks like negative margins.
The Problem: Obscured Anchor Links
When a user clicks an internal link containing a fragment identifier
(e.g., <a href="#section-2">), the browser aligns the
top edge of that target element directly with the top edge of the
viewport.
If your website has a fixed or sticky navigation bar, that bar sits
on top of the viewport. As a result, the first few lines of text or the
section heading are hidden underneath the navigation bar. Historically,
developers solved this using workaround techniques, such as adding a
large padding-top paired with an equivalent negative
margin-top, or inserting hidden <span>
tags positioned above the target. These workarounds often introduced
layout glitches, disrupted click targets, or bloated the HTML
markup.
What is scroll-margin?
The scroll-margin property specifies the margins of an
element's scroll snap area. While originally introduced as part of the
CSS Scroll Snap Module, modern browsers apply these values whenever an
element is brought into view through targeted scrolling.
Unlike standard CSS margin, scroll-margin
does not affect the element’s physical position in the document flow,
nor does it create spacing between adjacent elements on the screen. It
is only evaluated when the browser determines where to position the
viewport during a scroll operation.
Basic Syntax and Usage
The scroll-margin property is a shorthand that sets
scroll-margin-top, scroll-margin-right,
scroll-margin-bottom, and scroll-margin-left
simultaneously. You can also use logical properties such as
scroll-margin-block and
scroll-margin-inline.
For fixed headers, the vertical offset is usually the main concern. Here is how to apply it:
/* Apply an offset to all targetable headings */
h1[id],
h2[id],
h3[id],
section[id] {
scroll-margin-top: 5rem;
}If your sticky header has a height of 60px, setting
scroll-margin-top: 80px will align the element 80 pixels
below the top of the viewport. This clears the 60-pixel header
completely and leaves 20 pixels of extra breathing room between the
header and the heading.
Key Benefits
- Clean Box Model: It leaves the element's actual
margin,padding, and dimensions intact, avoiding visual layout shifts. - Dynamic Spacing: It works automatically with smooth
scrolling (
html { scroll-behavior: smooth; }), ensuring the animation stops precisely at the defined offset. - Responsive Control: You can adjust the offset inside media queries to adapt to different header heights across desktop and mobile screens:
:target {
scroll-margin-top: 4rem;
}
@media (min-width: 768px) {
:target {
scroll-margin-top: 6rem;
}
}Using :target allows you to apply
scroll-margin-top dynamically to whichever element matches
the current URL fragment identifier.
Browser Support
The scroll-margin property enjoys broad support across
all modern desktop and mobile browsers, including Chrome, Edge, Firefox,
and Safari. Implementing it provides a reliable, CSS-only solution to
anchor link positioning with zero JavaScript overhead.