# Getting stuck: all the ways position:sticky can fail

CSS's `position: sticky` is a dream come true for web developers. It allows elements to switch between relative and fixed positioning based on the scroll position, without needing any JavaScript. It's great for persistent headers, sidebars or any element you want to keep in view while scrolling. It's one of five [positioning schemes](/blog/understanding-the-fundamentals-of-css-layout/#positioning-schemes) in CSS, alongside static, relative, absolute and fixed.

However, it can be surprisingly tricky to work with because it comes with a number of constraints, and you can end up in a situation where you think an element should be stuck, but it's just not working. In this post, we'll go over how `position: sticky` can fail and how to fix these issues.

## Positioning in CSS

The `position` property tells you the type of positioning to use for an element, and you can think of those as "mini layout systems" that determine how elements are placed in relation to each other. There are four main values for the `position` property:

*   `static`: The default value. Elements are positioned according to the normal flow of the document.
*   `relative`: Elements are positioned relative to their normal position.
*   `absolute`: Elements are positioned relative to the offset parent (the nearest ancestor with a position other than static).
*   `fixed`: Elements are positioned relative to the viewport.

Position: sticky is the fifth one, and it's a hybrid of `position: relative` and `position: fixed`. It behaves like a relatively positioned element until it reaches a specified scroll position, at which point it becomes fixed. So it lets you switch between two mini layout systems, which means that you need to take the rules of both systems into account.

## How sticky works

1.  Your element is relatively positioned to its normal position (meaning it's in the flow of the document) and scrolls along like any other element.
2.  It reaches a specific scroll position (defined by `top`, `bottom`, `left`, or `right`), at which point it becomes fixed.
3.  It stays fixed until it touches the bottom of its parent element (the parent element isn't stuck so still scrolls up), at which point it returns to its normal position.

## What position:sticky needs to work

For `position: sticky` to work, the browser needs to know _when_ to switch between relative and fixed positioning.That specific scroll position is specified by `inset` values: `top`, `right`, `bottom`, and `left`.

These inset value is measured from the nearest scrolling area, which usually is the viewport. A scrolling area is an element that has `overflow` set to either `scroll` or `auto` (or `hidden`, more on that later) and has an explicit height. The viewport is the visible part of the page in your browser, and it's a scrolling area by default.

That sticky element needs to be shorter than the scrolling area and shorter than its parent element (if there is a parent element between it and the scrolling area). If the sticky element is taller than the scrolling area, it will never be fully visible when reaching the inset value so can't get stuck and if it's the same height or taller than the parent element, that parent element will continue scrolling away, and the sticky element will scroll away with it.

*   An element with `position: sticky` and an `inset` value.
*   A scrolling area that the sticky element is in.
*   The sticky element needs to be shorter than its parent element (if it has one).
*   The sticky element needs to be shorter than the scrolling area.

Each of these requirements can be a source of failure, and we'll go over them in more detail.

## Scrolling area issues

One of the most common reasons for `position: sticky` to fail is related to its containing element, which is either the scrolling area that your element is in, or the viewport.

### Issue: overflow: hidden

`Overflow:hidden` creates a new scrolling area: one without a scroll bar but that can still be scrolled programmatically.

That means that if you have `overflow:hidden` (or `auto` or `scroll`, for more explicit scrolling) on an ancestor of the sticky element it's now checking the inset values against that element instead of against the viewport, and they probably won't get stuck when you expect them to.

Note that when you set an `overflow-x: hidden` somewhere, then it also forces `overflow-y: auto` on the same element, so you still create a new scrolling area.

```html
<div class="container">
  <div class="sticky-element">I should be sticky</div>
</div>
```

```css
.container {
  overflow: visible; /* or clip */
}
```

Make sure you're not creating a new scrolling area between your sticky element and the viewport (unless you explicitly want that). If you really do need `overflow: hidden`, switch it out to `overflow: clip`, which hides the overflow but also disables programmatic scrolling. Learn more about that in [Do you know about overflow:clip?](https://kilianvalkhof.com/2022/css-html/do-you-know-about-overflow-clip/).

[

### Polypane has everything you need to build great sites

Learn about Polypane](/)

### Issue: no explicitly set height

An element can only scroll if it has a specified height, so when it doesn't, the browser can't determine when the sticky element should stick.

```html
<div class="container">
  <div class="sticky-element">I should be sticky</div>
</div>
```

```css
.container {
  height: 400px; /* or any appropriate height */
  overflow-y: visible;
}
```

This also applies to any element that's between the sticky element and the scrolling area.

Your container should be taller than the sticky element. Without an explicit height it will be as high as the content inside of it. Sometimes that works when you have more than just the sticky element inside of it because the other elements inside the container cause it to be high enough for the sticky element to get stuck.

## Sticky element issues

The sticky element itself can also be the source of positioning failures.

### Issue: missing inset value

This might be a bit obvious, but it's easy to forget: a sticky element needs at least one of `top`, `bottom`, `left`, or `right` to be set so the browser knows where to stick it.

Failing example:

```css
.sticky-element {
  position: sticky;
  /* Missing top, bottom, left, or right value */
}
```

Fix:

```css
.sticky-element {
  position: sticky;
  top: 0; /* Add at least one of top, bottom, left, or right */
}
```

### Issue: element larger than container (or viewport)

If the sticky element is larger than its container, it won't stick, because the sticky element needs to be fully visible before it can get stuck.

Failing example:

```css
.sticky-element {
  position: sticky;
  top: 0;
  height: 110%; /* Taller than container */
}
```

You can't always control how height an element is so if you can't change the height of the element, you can limit the height of the sticky element:

```css
.sticky-element {
  position: sticky;
  top: 0;
  max-height: 50%; /* Limit height to container */
  overflow-y: auto; /* Allow scrolling within element if needed */
}
```

If you make it as high as the container then it will never stick, so you need to make sure it's shorter than the container for it to remain in view.

## Flex and grid layout interactions

Flexbox and Grid layouts can introduce additional complexities when working with sticky positioning, because these layout systems also influence properties on the elements inside them.

### Flexbox Issue

In a flex container, sticky elements may not stick as expected because a flex layout doesn't create a scrolling area by default (its height is dependent on its children). You need to set a height on the flex container and allow scrolling to make the sticky element work.

```html
<div class="flex-container">
  <div class="sticky-element">I should be sticky</div>
  <div class="flex-item">Flex content</div>
</div>
```

```css
.flex-container {
  display: flex;
  flex-direction: column;
  height: 400px; /* Set a height on the flex container */
  overflow-y: auto; /* Allow scrolling */
}
```

### Grid and flex: align-items

Grid and flex layouts default to `align-items: stretch`, which means that the sticky element is as high as the containing grid cell. That means that whenever it reaches the `inset` and should get stuck, it's instantly unstuck again because the parent element scrolls away. You can fix this by setting `align-items: start` on the grid container.

```html
<div class="grid-container">
  <div class="sticky-element">I should be sticky</div>
  <div class="grid-item">Grid content 1</div>
  <div class="grid-item">Grid content 2</div>
</div>
```

```css
.grid-container {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
  height: 400px;
  overflow-y: auto;
  align-items: start; /* This is the key change */
}
.sticky-element {
  position: sticky;
  top: 0;
  height: auto; /* Allow the element to size to its content */
}
```

Alternatively, you can also set `align-self: start` on the sticky element, if you want the other element to keep stretching.

## Conclusion

`position: sticky` is a powerful tool for creating sticky elements without JavaScript, but it has constraints that aren't immediately apparent. To make sure your sticky elements work as expected, you need to ensure that:

*   The sticky element is in a scrolling area (and in the scrolling area you expect)
*   The sticky element has an `inset` value.
*   The sticky element is shorter than the scrolling area.

By understanding these constraints, you can make sure that your sticky elements work as expected!

## Debugging sticky elements in Polypane

Most of the issues above come down to an ancestor you didn't expect to be the scrolling area, or a height you didn't expect to be constrained. Polypane's [layout debugging](/docs/layout-debugging/) mode outlines every element on the page and shows you `position`, `overflow` and `display` for anything you select, so you can spot the offending ancestor without bisecting your CSS by hand.

---

Polypane is the browser built for developers and designers — try it at https://polypane.app.
