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:
- Defines the Active Viewing Area: The browser
calculates scroll operations against the area inside the
scroll-paddingboundaries rather than the outer edge of the viewport. - 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 thescroll-padding-topboundary instead of the top edge of the container. - Respects Anchor Links: Browsers that support
scroll-paddingautomatically 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-paddingis 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-marginis 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.