Interactive CSS debugging lab

CSS position: sticky not working? Diagnose the root cause

HighIntermediateCss

Quick diagnosis

A sticky element needs a non-auto inset on the sticky axis, the intended scroll container, and enough room inside its containing block to move. Flex or grid stretching can remove that room, while stacking contexts can make a correctly sticky element look hidden. Inspect computed styles and geometry before changing z-index.

Interactive CSS debugging lab

Diagnose why position: sticky is not working

Check the computed inset, actual scroll container, available travel, flex or grid alignment, and paint order before changing unrelated CSS.

Missing inset

Add a non-auto inset on the sticky axis, such as top: 1rem.

Unexpected scroll container

Inspect ancestor overflow and verify which element actually scrolls.

No travel room

Compare the sticky element, containing block, and scroll range.

Flex or grid stretch

Verify item sizing before applying align-self: start.

Hidden behind another layer

Prove sticky behavior first, then inspect stacking contexts and z-index.

Interview focus

This CSS interview question tests whether you can explain CSS Sticky Not Working? 5 Causes and Fixes, connect it to production trade-offs, and handle common follow-up questions.

  • CSS Sticky Not Working? 5 Causes and Fixes explanation without falling back to memorized definitions
  • Position and Layout reasoning, edge cases, and production failure modes
  • How you would answer the most likely CSS interview follow-up
Practice more CSS interview questions
Interview answer drill

Use this CSS interview question to rehearse a quick answer, common mistake, follow-up, and production pitfall.

CSS sticky failure modes, proof, and fixes

Short answer

When position: sticky appears not to work, first prove four things: the element has a non-auto inset such as top: 0; you are scrolling the container sticky actually uses; that container has scroll range; and the sticky box has room to travel inside its containing block. If those checks pass but the element is covered, inspect paint order and stacking contexts. A larger z-index cannot repair missing scroll geometry.

Symptom

Likely cause

DevTools evidence

Smallest justified fix

It behaves like relative positioning

Both insets on the sticky axis compute to auto

Computed top/bottom or logical insets are auto

Set one axis-appropriate inset, such as inset-block-start: 0

It follows the wrong scroller

An ancestor establishes the nearest scroll container

Ancestor computed overflow and scroll range identify a different scroll owner

Keep the intended scroller and give it real range, or remove the accidental overflow rule

It sticks only briefly or never visibly travels

The containing block is too short, or the sticky box consumes the available runway

Sticky height approaches parent height; parent end constrains the box immediately

Increase the containing block's useful height or reduce the sticky box's block size

A flex/grid sidebar will not appear to move

Cross-axis stretching removes useful travel in this layout

Computed align-self and equal item/container dimensions confirm stretching

Use align-self: start only when the measurements prove stretch is the cause

It sticks but disappears behind content

Paint order or an ancestor stacking context covers it

The top coordinate is correct, but elementFromPoint() finds another painted element

Fix the owning stacking context, then use the smallest layer value required

Treat sticky as a geometry problem first and a layering problem only after geometry passes.

Five failure cases in the debugging lab

The lab above isolates one variable at a time: missing-inset, unexpected-scroll-container, no-travel-room, flex-grid-stretch, and sticks-but-hidden. Run the inspector before applying a fix. That baseline matters because several fixes can make the demo look different while leaving the original root cause unproven.

Inspect the ancestor chain in this order

  1. Confirm the target's computed position is sticky; a more specific selector may have overridden it.
  2. Check the inset for the axis you expect to stick. If both relevant insets are auto, sticky behaves like relative positioning on that axis.
  3. Walk ancestors and record computed overflow-x/overflow-y. Do not assume the page viewport is the scroll owner.
  4. <li>For the actual scroll container, compare scrollHeight with clientHeight and verify its scrollTop changes.
  5. Compare the sticky box with its containing block. Sticky is constrained when the opposite edge of that block is reached.
  6. In flex and grid layouts, inspect align-items, align-self, and measured heights before changing alignment.
  7. Only after the position changes correctly, inspect stacking-context owners and which element is painted at the sticky coordinates.

Broken and fixed example

This markup has an intentional inner scroller. The broken rule declares sticky but provides no threshold on the block axis.

HTML
<div class="results">
  <aside class="filters">Filters</aside>
  <main class="items">
    <!-- enough results to make .results scroll -->
  </main>
</div>
                  
CSS
.results {
  display: grid;
  grid-template-columns: 12rem minmax(0, 1fr);
  align-items: start;
  max-block-size: 24rem;
  overflow-y: auto;
}

/* Broken: no non-auto block-axis inset. */
.filters {
  position: sticky;
}

/* Fixed for this measured layout. */
.filters {
  position: sticky;
  inset-block-start: 0;
}
                  

The fix is specific to the evidence. overflow-y: auto is correct here because .results is deliberately the scroller and has a constrained block size plus overflowing content. Do not copy a blanket rule that every ancestor must use overflow: visible. Likewise, align-self: start is useful when stretching removed the runway; it is not required for every sticky element.

Sticky is not fixed

A sticky box remains in normal flow and is offset against a sticky view rectangle associated with its nearest relevant scrollport. It is also constrained by its containing block, so it stops sticking when that boundary reaches it. A fixed box is out of flow and normally uses the initial containing block. If the requirement is “stay visible for the entire viewport regardless of section boundaries,” that may be a fixed-position requirement rather than a broken sticky implementation. Review the CSS positioning model for relative, absolute, and fixed boxes before changing the behavior.

When z-index is relevant

position: sticky creates a stacking context, but that does not guarantee it will paint above a sibling in another ancestor context. If controlled scrolling shows the box holding the correct inset while content still covers it, inspect the parent contexts, paint order, and backgrounds. Fix ownership at the appropriate layer boundary instead of escalating to z-index: 999999. The z-index and stacking-context guide covers that failure separately. A transform on an ancestor can affect containing blocks or stacking, but it does not universally disable sticky positioning; prove the actual geometry and paint result.

Turn the diagnosis into assertions

  • The intended scroll owner has scrollHeight > clientHeight, and a test can change its scrollTop.
  • Before the threshold, the sticky box moves with normal flow; after the threshold, its top remains within a small pixel tolerance of the resolved inset.
  • The sticky box never crosses the ending edge imposed by its containing block.
  • At the expected sticky coordinates, document.elementFromPoint() resolves to the sticky box or one of its descendants when it should be visible.
  • Repeat at narrow and wide container sizes because content wrapping can change both scroll range and runway. Continue with responsive CSS constraints for that part of the test matrix.

Standards check

The terminology and axis behavior here follow the CSS Positioned Layout specification: sticky insets reference the nearest relevant scrollport, and the box remains constrained by its containing block. MDN's position reference also documents the non-auto inset requirement and why the nearest ancestor with a scrolling mechanism may not be the ancestor you expected to scroll.

Summary

  • Start with computed position and a non-auto inset on the sticky axis.
  • Identify the actual scroll container from ancestor overflow and measured scroll range.
  • Measure containing-block runway and flex/grid stretching instead of guessing.
  • Use z-index only when scrolling proves the box is sticky but paint-order evidence shows it is covered.
  • Test geometry at responsive widths and keep each fix tied to the failure it actually proves.
Similar questions
Guides
Preparing for interviews?