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.

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

  1. Clean Box Model: It leaves the element's actual margin, padding, and dimensions intact, avoiding visual layout shifts.
  2. Dynamic Spacing: It works automatically with smooth scrolling (html { scroll-behavior: smooth; }), ensuring the animation stops precisely at the defined offset.
  3. 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.