Fix: CSS position: sticky Not Working
Quick Answer
How to fix CSS position sticky not working — element scrolls away instead of sticking, caused by overflow hidden on a parent, missing top value, wrong stacking context, or incorrect height on the container.
The Error
You set position: sticky on an element but it scrolls away with the page instead of sticking. There is no error — the element just behaves like position: relative.
Common symptoms:
- A sticky header scrolls off the screen instead of staying at the top.
- A sticky sidebar stops working after adding
overflowto a parent element. position: stickyworks in one browser but not another.- The element sticks at the wrong position.
- The sticky element sticks only briefly then disappears.
- Adding
z-indexto make it appear above other content breaks the stickiness.
Why This Happens
position: sticky is a hybrid of relative and fixed — the element scrolls with the page until it hits the threshold (top, bottom, left, or right), then sticks. It fails when:
- No
top,bottom,left, orrightvalue is set — without a threshold, sticky has no reference point. - A parent or ancestor has
overflow: hidden,overflow: auto, oroverflow: scroll— the element sticks relative to its scroll container, which in this case is the overflowing ancestor, not the viewport. - The parent container is not tall enough — sticky only works while the element is inside its parent. If the parent is the same height as the sticky element, there is nowhere to scroll.
- The sticky element is a flex or grid child with
align-items: stretch— the element fills its container and has no room to stick. - Wrong stacking context —
z-index,transform,filter, orwill-changeon an ancestor creates a new stacking context that clips stickiness.
Fix 1: Always Set a top (or bottom) Value
position: sticky requires at least one directional offset. Without it, the browser does not know where to stick the element:
Broken — no threshold:
.header {
position: sticky;
/* Missing top: 0 — sticky has no effect */
background: white;
}Fixed:
.header {
position: sticky;
top: 0; /* Sticks when it reaches the top of the scroll container */
background: white;
z-index: 100;
}Use top for elements that stick at the top, bottom for those that stick at the bottom. You can also use top: 20px to leave a gap between the element and the top of the viewport.
Pro Tip: Always add a
z-indexto sticky elements. As you scroll, content passes underneath the sticky element — without az-index, other elements may render on top of it, making it appear broken when it is actually working correctly.
Fix 2: Remove overflow: hidden from Parent Elements
This is the most common cause of broken sticky positioning. Any ancestor with overflow set to hidden, auto, or scroll becomes the sticky element’s scroll container — and if that container does not scroll, the element sticks within it but appears not to work.
Broken:
.page-wrapper {
overflow: hidden; /* Breaks sticky for all descendants */
}
.sidebar {
position: sticky;
top: 20px;
/* Sticks inside .page-wrapper, but .page-wrapper doesn't scroll — appears broken */
}Fixed — remove overflow or change to overflow-x only:
.page-wrapper {
/* Remove overflow: hidden entirely */
/* Or if you only need to hide horizontal overflow: */
overflow-x: clip; /* clip does not create a scroll container (unlike hidden) */
}
.sidebar {
position: sticky;
top: 20px;
}overflow: clip vs overflow: hidden: overflow: hidden creates a new scroll container, breaking sticky. overflow: clip (CSS Overflow Level 4) clips content without creating a scroll container, preserving sticky behavior. Browser support: Chrome 90+, Firefox 81+, Safari 16+.
Find the problematic ancestor:
// Run in browser console to find which ancestor has overflow set
let el = document.querySelector(".your-sticky-element");
while (el) {
const style = getComputedStyle(el);
if (["auto", "scroll", "hidden"].includes(style.overflow) ||
["auto", "scroll", "hidden"].includes(style.overflowY)) {
console.log("Found overflow ancestor:", el, style.overflow, style.overflowY);
}
el = el.parentElement;
}Fix 3: Ensure the Parent Container Is Taller Than the Sticky Element
Sticky only works within the element’s parent container. If the parent is the same height as the sticky element, or shorter, there is no scroll distance within the parent and the element never sticks:
Broken — parent is same height as sticky element:
<div class="card"> <!-- height: 200px -->
<div class="sticky-label">Label</div> <!-- height: 200px, position: sticky -->
<!-- No content below sticky element in the parent -->
</div>Fixed — parent must be taller than the sticky element:
<div class="container"> <!-- height: 1000px -->
<div class="sticky-sidebar"> <!-- position: sticky; top: 0 -->
<!-- sidebar content -->
</div>
<div class="main-content"> <!-- lots of content -->
<!-- This content makes the container tall enough -->
</div>
</div>The sticky element sticks as long as it is within its parent. Once the parent scrolls out of view, the sticky element goes with it. This is intentional — it is how sticky works within table cells, sidebars, and sectioned lists.
Fix 4: Fix Sticky in Flexbox and Grid
When the sticky element is a flex or grid child, the parent’s alignment settings can prevent sticking:
Broken — flex child fills full height:
.layout {
display: flex;
align-items: stretch; /* Default — flex children fill the container height */
}
.sidebar {
position: sticky;
top: 0;
/* Sidebar is stretched to match .main-content height — no room to stick */
}Fixed — use align-self on the sticky element:
.layout {
display: flex;
align-items: stretch;
}
.sidebar {
position: sticky;
top: 0;
align-self: flex-start; /* Don't stretch — let sticky work */
}align-self: flex-start prevents the sidebar from being stretched to the container height, allowing it to scroll and stick independently.
For grid:
.grid-layout {
display: grid;
grid-template-columns: 250px 1fr;
align-items: start; /* Or use align-self: start on the sticky child */
}
.sticky-sidebar {
position: sticky;
top: 20px;
}Fix 5: Fix z-index and Stacking Context Issues
transform, filter, perspective, will-change, and some other CSS properties create a new stacking context on an ancestor, which can interfere with sticky positioning:
Broken — transform on ancestor breaks sticky:
.animated-wrapper {
transform: translateZ(0); /* Creates stacking context — breaks sticky descendants */
/* Also: will-change: transform; filter: blur(0); */
}
.sticky-header {
position: sticky;
top: 0;
/* May not stick correctly if inside .animated-wrapper */
}Diagnosis — check for transforms on ancestors:
let el = document.querySelector(".sticky-header");
while (el) {
const style = getComputedStyle(el);
if (style.transform !== "none" || style.willChange !== "auto" || style.filter !== "none") {
console.log("Stacking context on ancestor:", el, {
transform: style.transform,
willChange: style.willChange,
filter: style.filter,
});
}
el = el.parentElement;
}Fix — move the transform to a different element or restructure the DOM so the sticky element is not inside the transformed ancestor.
Sticky element’s own z-index:
.sticky-header {
position: sticky;
top: 0;
z-index: 50; /* Must be higher than overlapping content */
background: white; /* Opaque background to cover scrolled content */
}Without an opaque background, content scrolling behind the sticky element shows through.
Fix 6: Fix Sticky Table Headers
position: sticky works on <th> and <td> elements for sticky table headers — but requires specific setup:
.table-container {
overflow-y: auto; /* The TABLE must scroll, not the page */
max-height: 400px; /* Give the container a fixed height */
}
table {
border-collapse: collapse;
width: 100%;
}
thead th {
position: sticky;
top: 0;
background: white; /* Required — otherwise rows show through */
z-index: 1;
}<div class="table-container">
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody>
<!-- Many rows -->
</tbody>
</table>
</div>border-collapse: collapse issue: When using border-collapse: collapse, sticky headers may lose their border on scroll. Fix by using box-shadow instead of border:
thead th {
position: sticky;
top: 0;
background: white;
box-shadow: 0 1px 0 #ccc; /* Replaces the bottom border */
}Fix 7: Fix Sticky on Mobile
position: sticky is well-supported in modern browsers (Chrome 56+, Firefox 59+, Safari 13+). On older iOS Safari (below 13), it requires the -webkit- prefix:
.sticky-element {
position: -webkit-sticky; /* Safari < 13 */
position: sticky;
top: 0;
}iOS Safari-specific issue with 100vh: On iOS, 100vh includes the browser UI (address bar), causing height calculation issues with sticky elements. Use dvh (dynamic viewport height) instead:
.full-height-container {
min-height: 100dvh; /* Adjusts for iOS browser UI */
}Still Not Working?
Test in isolation. Create the simplest possible sticky example (one sticky element, one tall parent, one threshold value) and verify it works. Then add complexity back until it breaks — that identifies the culprit.
Check position: sticky support for your use case. Sticky within table cells, multi-column layouts, and certain flex/grid configurations can have browser-specific quirks. Check MDN for known limitations.
Check if JavaScript is overriding position. If a script sets element.style.position = "relative" or similar after page load, it overrides your CSS. Check the Computed styles in DevTools and look for inline styles overriding your stylesheet.
For related layout issues, see Fix: CSS Flexbox not working and Fix: CSS Grid not working. For z-index stacking issues, see Fix: CSS z-index not working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: CSS Custom Properties (Variables) Not Working or Not Updating
How to fix CSS custom properties not applying — wrong scope, missing fallback values, JavaScript not setting variables on the right element, and how CSS variables interact with media queries and Shadow DOM.
Fix: CSS Animation Not Working (@keyframes Has No Effect)
How to fix CSS animations not working — @keyframes not applying, animation paused, transform conflicts, reduced motion settings, and common animation property mistakes.
Fix: CSS Grid Not Working (display: grid Has No Effect)
How to fix CSS Grid not working — grid-template-columns has no effect, grid items not placing correctly, implicit vs explicit grid confusion, and common grid layout debugging techniques.
Fix: CSS Flexbox Not Working (display: flex Has No Effect)
How to fix CSS Flexbox not working — why display flex has no effect, flex children not aligning, flex-grow not stretching, and how to debug common Flexbox layout issues.