How to Use CSS position: sticky
Learn how position: sticky works, how to make headers and headings stick while scrolling, and how to fix common layout problems.
position: sticky keeps an element in normal document flow until it reaches a scroll threshold, then holds it there while its containing block allows. For a header that sticks to the top, set position: sticky and a non-auto inset such as top: 0:
.site-nav {
position: sticky;
top: 0;
z-index: 10;
}
If it does not stick, check the inset, ancestor overflow, and the height of its containing block. Sticky positioning is not simply fixed positioning that activates after scrolling.
1. What sticky positioning does
A sticky element is laid out in its normal position first. As the page or relevant scroll container moves, the browser offsets the element to keep it within the inset threshold. It remains bounded by its containing block and stops when that block’s opposite edge is reached. Its original layout space remains reserved, so following content does not move into the gap it occupied.
Sticky positioning also creates a stacking context. Set z-index when the element should layer above nearby content, and check other stacking contexts if it still appears behind something.
2. Make an element stick
- Choose the axis: for a top-sticking element use
toporinset-block-start; for bottom usebottomorinset-block-end. - Set
position: stickyand a non-autoinset on that axis. - Make sure the element has room to move inside its containing block.
- Inspect ancestor overflow and stacking if the result differs from the intended layout.
Sticky navigation bar
<header class="site-header">
<nav aria-label="Primary">...</nav>
</header>
<main>...</main>
.site-header {
position: sticky;
top: 0;
z-index: 10;
background: white;
}
The background prevents scrolled content from showing through the bar. The z-index is for the intended overlay; sticky already establishes a stacking context.
Sticky section headings
.section-heading {
position: sticky;
top: 0;
z-index: 1;
background: white;
}
For headings beneath a persistent header, offset them by the header’s height so they do not stick underneath it:
:root { --header-height: 3.5rem; }
.section-heading {
position: sticky;
top: var(--header-height);
z-index: 1;
background: white;
}
Logical insets and bottom sticking
Logical properties follow writing direction and are useful in layouts that support multiple writing modes:
.sticky-block-start {
position: sticky;
inset-block-start: 0;
}
.sticky-inline-start {
position: sticky;
inset-inline-start: 0;
}
To stick to the bottom edge instead, use bottom: 0 or inset-block-end: 0. The element still needs a suitable containing block and scroll context. If both insets on an axis are auto, it behaves like relative positioning on that axis.
3. Understand the scroll ancestor and containing block
Sticky follows the nearest ancestor that establishes a scrolling mechanism. An ancestor with overflow: hidden, auto, or scroll can become that reference even when a different outer region is visibly scrolling. The sticky element can travel only within its containing block, so a short parent can make it stop almost immediately.
When debugging, inspect the DOM ancestors from the sticky element outward. Identify overflow values first, then verify which parent bounds its movement and whether that parent is tall enough for the intended sticky interval.
4. Sticky versus fixed
| Behavior | sticky |
fixed |
|---|---|---|
| Layout space | Retains its normal-flow space | Removed from normal flow |
| Movement reference | Nearest scrolling mechanism and containing block | A different positioning mode, generally positioned relative to the viewport |
| Travel limit | Bounded by the containing block | Not bounded by the normal-flow parent in the same way |
Use sticky for elements that should participate in the page layout and stop at a section boundary. Use fixed when the element should remain anchored independently of that flow.
5. Troubleshoot sticky positioning
| Symptom | Likely cause | Fix |
|---|---|---|
| It never sticks | No non-auto inset on the intended axis |
Set top, bottom, or the corresponding logical inset. |
| It sticks in the wrong area | An ancestor’s overflow creates the nearest scrolling mechanism | Inspect ancestors for hidden, auto, or scroll; adjust the layout or intended scroll container. |
| It stops too soon | The containing block ends, or the parent is too short | Check the DOM structure and give the containing block enough height for the desired travel. |
| It covers or sits behind content | Layering or background is not configured as intended | Set an appropriate z-index and background; inspect surrounding stacking contexts. |
| It overlaps a persistent header | The sticky inset is smaller than the header height | Set the inset to the header height, ideally through a shared CSS custom property. |
6. Inspect the result in a browser
- Scroll through the page and confirm where the element begins sticking and where it stops.
- Repeat while scrolling any nested panel that could be the nearest scrolling mechanism.
- Inspect computed
position, inset, and ancestoroverflowvalues in browser developer tools. - Check whether the containing block is tall enough and whether another stacking context obscures the element.
A screenshot can help compare a page before and after scrolling or document a layout issue. ScreenshotNeo is a website screenshot API and MCP server by ScreenshotNeo; its capture options include viewport and full-page screenshots, device presets, and custom CSS or JavaScript. See the ScreenshotNeo API documentation.
Or skip the browser setup
One GET request returns an image or PDF. This runnable cURL example saves a WebP screenshot:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
Equivalent Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Equivalent Node.js:
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
Cookie banners, newsletter popups, and chat widgets are removed before the shot, and those steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers report the page verdict and billing status. An MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Every feature is on every plan. See the API docs, then sign up for 1,000 free screenshots a month with no card.
7. Performance, reliability, and cost
Sticky positioning is a browser-native layout feature, so the main practical checks are whether the correct scroll ancestor is involved, the containing block permits movement, and overlays layer as intended. Avoid adding JavaScript scroll handlers to recreate behavior CSS already provides unless the interaction needs behavior sticky cannot express.
For screenshot-based review, capture the same viewport and scroll position when comparing changes. A full-page image is useful for page structure, while a viewport image focuses on what is visible at one moment. With ScreenshotNeo, only clean shots are billed; failed or blocked captures and cache hits cost nothing. Choose a cache TTL when repeat captures can use cached output. Bulk capture supports up to 100 URLs per call; async jobs with signed webhooks are available for longer workflows. Check the usage API to monitor consumption and the response headers to see each result’s verdict and billing status.
8. FAQ
Does sticky work without a scroll?
It can appear unchanged if the page or relevant scroll container never moves far enough to reach the inset threshold.
Can multiple headings stick?
Yes. Give each heading a sticky inset. When several share the same threshold, account for overlap with distinct offsets or layout decisions.
Why does overflow: hidden affect sticky?
It can establish the nearest scrolling mechanism that sticky follows, changing the scroll reference from the one you expected.
Does sticky reserve space?
Yes. The element remains in normal flow and keeps its assigned layout space.


