Skip to content
All articles
CSS debugging

position: sticky Not Working? 8 Causes and How to Fix Each

position: sticky not working? Check these 8 causes in order: missing top offset, overflow on a parent, short parents, flex/grid stretch, z-index, tables and Safari.

8 min read - Updated 2026-10-20

When position: sticky is not working, nothing throws an error. The element just scrolls away as if sticky were never declared. It almost always comes down to one of eight causes. Work down this list in order (the first two account for most cases) and you will find the culprit fast.

1. You forgot the threshold offset

This is the number one cause. Sticky needs at least one of top, right, bottom or left to know where to pin. Without it the declaration is valid but never engages.

.sidebar {
  position: sticky;
  top: 0; /* without an offset, sticky never engages */
}

Add top: 0 (or another edge) to the sticky element itself

The offset is measured from the scroll container's edge, not the viewport

Under a fixed header? Use the header's height as the top value

Check DevTools' Computed panel to confirm top isn't overridden to auto

2. A parent has overflow hidden, auto or scroll

If any ancestor between the sticky element and the page has overflow set to hidden, auto or scroll, it becomes the scroll container and the element sticks to that box instead, usually invisibly. The classic culprit is overflow-x: hidden on a page wrapper, added to hide a horizontal scrollbar. Paste this into the DevTools console to find every offending ancestor.

let el = document.querySelector(".sticky");
while ((el = el.parentElement)) {
  const { overflowX, overflowY } = getComputedStyle(el);
  const ok = (v) => v === "visible" || v === "clip";
  if (!ok(overflowX) || !ok(overflowY)) console.log(el, overflowX, overflowY);
}

Run the snippet, replacing .sticky with your element's selector

Replace overflow-x: hidden with overflow-x: clip: it clips without creating a scroll container

Or remove the overflow and fix whatever was overflowing

If the ancestor must scroll, that's fine: the element will stick inside it

3. The parent is too short

Sticky is constrained to its direct parent. If the parent is only as tall as the sticky element, there is no room to travel, so it looks like it isn't sticking at all. If it sticks for a while and then scrolls away halfway down the page, that is the parent ending.

Give the parent enough height for scrolling to happen inside it

Move the sticky element up the tree to a taller ancestor

Watch for wrappers that only exist for styling and shrink the container

Outline the parent in DevTools to see exactly where it ends

4. It's a grid or flex item that stretches

Grid and flex items stretch to fill the row by default (align-items: stretch). A sticky sidebar inside display: flex or display: grid is therefore already as tall as its container and has nowhere to move.

.layout { display: flex; gap: 2rem; }

.sidebar {
  position: sticky;
  top: 80px;
  align-self: flex-start; /* stop the stretch so it can travel */
}

Add align-self: start to the sticky item

Or set align-items: start (flex-start) on the container

Confirm in DevTools that the item's height now matches its content

Long sidebars: add max-height and overflow-y: auto to the item itself

5. It is sticking, but something covers it

Sometimes sticky works and you just can't see it: later elements with position, transform or opacity paint on top, or the sticky element has a transparent background so content shows through.

Give the sticky element a background color

Add a z-index higher than the content that scrolls under it

Check for a new stacking context on the parent (transform, filter, opacity < 1)

Temporarily add outline: 2px solid red to see where it really is

6. The offset fights another element

A sticky element with top: 0 under a fixed header will stick behind that header. Two sticky elements with the same offset replace each other instead of stacking.

Set top to the fixed header's height (a CSS variable keeps them in sync)

Stack multiple sticky bars by giving each a larger top value

Recalculate offsets at breakpoints where the header height changes

Use scroll-margin-top so anchor jumps don't land under the sticky bar

7. Tables: sticky on the wrong element

For sticky table headers, put position: sticky on the th cells (thead th) rather than the table. With border-collapse: collapse, borders on sticky cells don't travel with them, so the header looks broken even though it sticks.

Target thead th { position: sticky; top: 0; }

Give th a background so rows don't show through

Use border-collapse: separate, or draw the border with box-shadow

For a scrolling table, put overflow: auto on a wrapper with a max-height

8. Old Safari or a width-specific bug

Every current browser supports position: sticky unprefixed. Safari versions before 13 needed position: -webkit-sticky. More often the bug is width-specific: a media query adds overflow, changes the layout to flex, or the header is taller than a landscape phone screen.

Add position: -webkit-sticky before position: sticky only if you still support very old iOS

Search your media queries for overflow and display changes

Test landscape phones, where sticky headers eat most of the viewport

Spot-check real Safari after fixing it in Chromium

Catch sticky bugs at every breakpoint in one scroll

Sticky bugs hide at specific widths, so checking one viewport at a time is how they reach production. Sizzy shows your localhost on phones, tablets and desktops side by side with scrolling synced: scroll once and you see every sticky header and sidebar engage and release on every device, then inspect any one of them with Chromium DevTools.

Synced scrolling across every device in one window

Full Chromium DevTools for each device

Landscape and custom device sizes for the short-screen cases

Framed screenshots to attach to the bug ticket

Release checklist

Threshold offset (top/bottom/left/right) is set on the sticky element.

No ancestor has overflow hidden/auto/scroll (use overflow: clip instead).

The parent container is tall enough to allow travel.

Grid/flex sticky items have align-self: start.

The sticky element has a background and a z-index.

Behavior verified at multiple viewport widths and in Safari.

Frequently asked questions

Why is position sticky not working at all?

In most cases you are missing a threshold offset: sticky does nothing without top, bottom, left or right. The next most likely cause is an ancestor with overflow: hidden, auto or scroll capturing the scroll context.

Does overflow: hidden break position sticky?

Yes, if it is on an ancestor between the sticky element and the page. That ancestor becomes the scroll container, and the element only sticks within it, which usually looks like it isn't sticking at all. Use overflow: clip instead, which clips without creating a scroll container.

Why is position sticky not working in a flex or grid container?

Flex and grid items stretch to the full height of the container by default, so the sticky item has no room to move. Add align-self: start (or flex-start) to the sticky item.

Why does sticky stop halfway down the page?

Sticky is constrained to its direct parent. Once the parent's bottom edge reaches the element, it un-sticks. If it stops earlier than expected, the parent is shorter than you think: give it more height or move the element up the tree.

Why is my sticky header not working on mobile?

Usually a media query adds overflow to a wrapper or switches the layout to flex at that width. Check the mobile styles for overflow and display changes, and test in a real mobile Safari as well as Chromium.

Do I still need position: -webkit-sticky?

Only for Safari versions before 13. Every current browser supports position: sticky without a prefix.

Sources and further reading

Related guides