CSS scroll-padding for Fixed Headers

Fixed headers frequently obscure content when navigating to page anchors or using CSS Scroll Snap, as browsers align snap positions directly against the physical edge of the viewport. The CSS scroll-padding property eliminates this issue by defining an offset inside the scroll container, effectively shrinking the visible target area—known as the "snapport"—so that content snaps into place below the fixed header without overlapping.

The Problem with Fixed Headers and Scrolling

When an element has position: fixed or position: sticky, it is removed from the normal document flow or floats above it. When users click an anchor link (such as #section-2) or use CSS Scroll Snap to cycle through items, the browser scrolls until the targeted element touches the top edge of the scroll container. Because the fixed header sits directly over that top edge, it covers the top portion of the target content.

How scroll-padding Solves the Issue

The scroll-padding property acts as an internal inset for the scrollable container. While regular CSS padding creates physical space that affects layout and background rendering, scroll-padding only affects scroll boundaries and the calculation of snap positions.

When you apply scroll-padding-top to a scroll container, you redefine its "snapport"—the region used to calculate scroll offsets:

  1. Defines the Active Viewing Area: The browser calculates scroll operations against the area inside the scroll-padding boundaries rather than the outer edge of the viewport.
  2. Offsets Snap Points Automatically: When an element aligns via scroll-snap-align: start, the browser aligns the top of the target element to the bottom of the scroll-padding-top boundary instead of the top edge of the container.
  3. Respects Anchor Links: Browsers that support scroll-padding automatically apply this offset to standard hash/anchor link navigation, preventing targeted headings from being hidden beneath navigation bars.

Implementation Example

To offset a fixed header that is 80 pixels high, define scroll-padding-top on the root scroll container (usually the html element or the custom element with overflow: scroll):

:root {
  /* Set the scroll padding equal to or slightly greater than the header height */
  scroll-padding-top: 80px;
}

header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 80px;
}

.container {
  scroll-snap-type: y mandatory;
}

.section {
  scroll-snap-align: start;
}

With this rule in place, whenever a .section snaps into place, the browser leaves an 80-pixel buffer at the top of the viewport, ensuring that the fixed header does not obscure any text or interactive elements.

scroll-padding vs. scroll-margin

While both properties solve the overlap problem, they target different parts of the layout:

  • scroll-padding is declared on the scroll container (the parent or :root). It globally offsets the entire snapping viewport, making it the ideal choice for persistent fixed components like site navigation or persistent toolbars.
  • scroll-margin is declared on the child elements. It sets individual offsets for specific targets, making it suitable when different sections require unique spacing thresholds or when you cannot modify the container's styles.