CSS scroll-padding-top Property
This property defines the offset distance from the top of the scroll container to the start of the scroll-snap area or the target element when navigating via anchor links.
| auto | The browser determines the offset automatically based on the user agent's default behavior. |
| <length> | Sets a fixed offset using standard units like px, rem, vh, or em. |
| <percentage> | Calculates the top offset as a percentage of the scroll container's height. |
Code Examples
A basic implementation showing how scroll-padding-top prevents a fixed header from overlapping a target section when using an anchor link.
<!DOCTYPE html>
<html>
<head>
<style>
html {
scroll-behavior: smooth;
/* Offsets the scroll by 80px to account for the fixed header */
scroll-padding-top: 80px;
}
nav {
position: fixed;
top: 0;
width: 100%;
height: 60px;
background: #222222;
color: #ffffff;
display: flex;
align-items: center;
padding: 0 20px;
}
section {
height: 100vh;
padding: 20px;
background: #f4f4f4;
border-bottom: 2px solid #dddddd;
}
</style>
</head>
<body>
<nav>Fixed Navbar</nav>
<div style="padding-top: 80px;">
<a href="#target">Jump to Target Section</a>
<section>Scroll down or click the link...</section>
<section id="target">
<h2>Target Section</h2>
<p>This heading won't be hidden behind the dark navbar.</p>
</section>
</div>
</body>
</html>An advanced example using CSS variables and JavaScript to dynamically adjust scroll-padding-top based on the actual height of a header element.
<!DOCTYPE html>
<html>
<head>
<style>
:root {
--header-offset: 0px;
}
#container {
height: 400px;
overflow-y: scroll;
scroll-snap-type: y mandatory;
scroll-padding-top: var(--header-offset);
border: 5px solid #333333;
}
.box {
height: 300px;
scroll-snap-align: start;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
color: #ffffff;
}
#dynamic-header {
background: #ff5500;
padding: 10px;
text-align: center;
}
</style>
</head>
<body>
<div id="dynamic-header">Dynamic Header (Height affects scroll)</div>
<div id="container">
<div class="box" style="background: #3498db;">Panel 1</div>
<div class="box" style="background: #e74c3c;">Panel 2</div>
<div class="box" style="background: #2ecc71;">Panel 3</div>
</div>
<script>
const header = document.getElementById("dynamic-header");
const root = document.documentElement;
function updateOffset() {
const height = header.offsetHeight;
// Set the scroll padding dynamically based on the header height
root.style.setProperty("--header-offset", height + "px");
}
window.addEventListener("load", updateOffset);
window.addEventListener("resize", updateOffset);
</script>
</body>
</html>Pro Tip
Instead of hardcoding a pixel value, use a CSS variable for your header height. Set --header-height: 80px; on the root and then set scroll-padding-top: var(--header-height);. If you ever change your header size in a media query, just update that one variable and your scroll offsets will automatically adjust across the whole site.
Deep Dive
Think of scroll-padding-top as a camera offset. Normally, when a browser scrolls to an element (like hitting an anchor link or snapping to a section), it aligns the top edge of that element exactly with the top edge of the scrollport. If you have a fixed navigation bar, that bar will sit on top of your content, hiding it. By applying scroll-padding-top to the parent scroll container, you are telling the browser to stop the scroll a specific distance before reaching the target. This creates a virtual gutter that respects your UI layout. It does not change the layout of the elements on the page; it only changes where the scroll container decides to stop its 'camera' relative to the content.
Best Practices
Always apply this property to the scroll container, which is usually the html element or a div with overflow set to scroll or auto. You should typically set this value to match the height of your sticky or fixed header. Using relative units like rem or vh is better than fixed pixels if your header size changes based on font settings or viewport height. This ensures your section headings are never cut off when users navigate through your site's links.
Common Pitfalls
A common mistake is trying to apply this to the child elements or the section headings themselves; it must be applied to the parent container that has the scrollbars. Also, remember that this property only affects scroll alignment. It won't act like a traditional CSS padding-top that pushes content down visually in the document flow. If you use a value that is too large, your content might appear too far down the screen when snapped, leaving a weird empty gap.
Accessibility
This property is a huge win for accessibility. When keyboard users tab through links or use fragment identifiers, the browser jumps to the content. Without scroll-padding-top, the focused element might be hidden behind a fixed header, making it impossible for the user to see what they just focused on. By using this property, you ensure the focal point of the page is always visible and clear to the user.
Dev Data Table: scroll-padding-top property
| default | auto |
| animatable | yes |
| inherited | no |
| experimental | no |
| year_intro | 2017 |
| year_standard | 2019 |
| js_syntax_1 | element.style.scrollPaddingTop |
| js_syntax_2 | element.style.setProperty("scroll-padding-top", "50px") |
| js_note | In JavaScript, use camelCase for the property name when accessing the style object directly, or use the kebab-case string when using the setProperty method. |
| browsers | { "Chrome": 69, "Edge": 79, "Firefox": 68, "Safari": 11, "Opera": 56, "Chrome Android": 69, "Safari on iOS": 11, "Samsung Internet": 10, "Opera Mobile": 48 } |