413

I want to make the navigation bar stick to the top of the viewport once a user scrolls the page, but it's not working and I have no clue why. If you can please help, here is my HTML and CSS code:

.container {
  min-height: 300vh;
}
.nav-selections {
  text-transform: uppercase;
  letter-spacing: 5px;
  font: 18px "lato",sans-serif;
  display: inline-block;
  text-decoration: none;
  color: white;
  padding: 18px;
  float: right;
  margin-left: 50px;
  transition: 1.5s;
}

.nav-selections:hover{
  transition: 1.5s;
  color: black;
}

ul {
  background-color: #B79b58;
  overflow: auto;
}

li {
  list-style-type: none;
}
<main class="container">
  <nav style="position: sticky; position: -webkit-sticky;">
    <ul align="left">
      <li><a href="#/contact" class="nav-selections" style="margin-right:35px;">Contact</a></li>
      <li><a href="#/about" class="nav-selections">About</a></li>
      <li><a href="#/products" class="nav-selections">Products</a></li>
      <li><a href="#" class="nav-selections">Home</a></li>
    </ul>
  </nav>
</main>
serraosays
  • 7,163
  • 3
  • 35
  • 60
Harleyoc1
  • 4,141
  • 2
  • 9
  • 10

37 Answers37

655

Check if an ancestor element has overflow set (e.g. overflow:hidden); try toggling it. You may have to inspect the DOM tree higher than you expect =).

This may affect your position:sticky on a descendant element.

Kalnode
  • 9,386
  • 3
  • 34
  • 62
  • 3
    This is the right answer. However, it can be difficult to find which parent element is causing the problem. I wrote a jquery script to help identify the overflow property on parents which identified the problem (just run it in your console). Because the script is a few lines I've added an answer containing the script below. – danday74 Jul 07 '18 at 10:07
  • 13
    I missed the "s" in your **"parent elements"** – Cedric Ipkiss Jul 16 '18 at 17:53
  • 11
    I also missed the "s":( and that was the issue. In many places people write that you should check only parent not all parents. I found my issue by checking if calling `$(stickyElement).parents().css("overflow", "visible")` in web console would help and it did. Then I located the distant parent with `overflow:hidden` style that caused the problem. I wish I read this answer more carefully. – Mariusz Pawelski Jul 24 '18 at 16:56
  • Very important that you may have to go higher up the DOM tree than expected. I usually set my tag to overflow-x: hidden and this can prevent vertical sticky behavior way down the DOM tree. – J.McLaren May 24 '19 at 01:27
  • 10
    If you need check for `overflow:hidden`, you can run this script in your browser console to check all parents/ancestors of a given element: https://gist.github.com/brandonjp/478cf6e32d90ab9cb2cd8cbb0799c7a7 – brandonjp Jun 06 '19 at 00:28
  • Even `overflow-x: hidden` will stop the element from sticking. However, `overflow-x: hidden` in the **body** tag will work. – Benjamin Intal Oct 08 '19 at 21:58
  • 1
    @BenjaminIntal not in my case, I had this overflow-x: hidden in the *body* tag and it was the reason it was blocking it to work properly. – Ángel Jiménez Oct 10 '19 at 10:17
  • If you're implementing this solution on a table where you need to freeze the columns and you have overflow property set on table. make sure you shift it to a parent element and not table. – Rohan Shenoy Jan 29 '20 at 07:25
  • 1
    how cme you guys know this kind of stuff. are we missing something(not reading documentation, or code or what?) or it comes naturally by experience . Please suggest – narasimha sriharsha Kanduri May 13 '20 at 19:01
  • Ok so after I played around a bit, I find that only your tables immediate parent only should not have an "overflow" attribute, doesn't matter what it's set to, it should not be present. So, I would say you don't have to go up the DOM tree. But otherwise this answer is crucial to understanding how the sticky works. – PrasadW May 04 '21 at 09:36
  • You may have to inspect the DOM tree higher than you expect =). +1 :D – devmrh May 28 '22 at 08:42
  • This answer is gold! I couldn't make my element sticky that is nested almost 7 elements after the element that has overflow property set to hidden. It can go all the way up to html tag I guess that will affect the position sticky. – Dorji Tshering Jul 20 '22 at 16:53
  • saved my life. so it really can be EVERY parent contaner that contains some overflow hidden. i couldnt believe that was the problem. – robjke Oct 22 '22 at 14:50
  • Why does it behave like this? – Qwerty Jul 31 '23 at 23:55
409

Sticky positioning is a hybrid of relative and fixed positioning. The element is treated as relative positioned until it crosses a specified threshold, at which point it is treated as fixed positioned.
...
You must specify a threshold with at least one of top, right, bottom, or left for sticky positioning to behave as expected. Otherwise, it will be indistinguishable from relative positioning. [source: MDN]

So in your example, you have to define the position where it should stick in the end by using the top property.

html, body {
  height: 200%;
}

nav {
  position: sticky;
  position: -webkit-sticky;
  top: 0; /* required */
}

.nav-selections {
  text-transform: uppercase;
  letter-spacing: 5px;
  font: 18px "lato", sans-serif;
  display: inline-block;
  text-decoration: none;
  color: white;
  padding: 18px;
  float: right;
  margin-left: 50px;
  transition: 1.5s;
}

.nav-selections:hover {
  transition: 1.5s;
  color: black;
}

ul {
  background-color: #B79b58;
  overflow: auto;
}

li {
  list-style-type: none;
}
<nav>
  <ul align="left">
    <li><a href="#/contact" class="nav-selections" style="margin-right:35px;">Contact</a></li>
    <li><a href="#/about" class="nav-selections">About</a></li>
    <li><a href="#/products" class="nav-selections">Products</a></li>
    <li><a href="#" class="nav-selections">Home</a></li>
  </ul>
</nav>
Sachin Burdak
  • 337
  • 1
  • 3
  • 13
Marvin
  • 9,164
  • 3
  • 26
  • 44
  • 239
    In general, this answer is not enough to sticky to work, parent also should not have overflow property. – ViliusL Apr 10 '18 at 07:41
  • Thank you for your comment. I agree with you that for other examples than the OP's my answer might not be sufficient. The other answers point this out :) – Marvin Apr 10 '18 at 07:49
  • 2
    I think the trick is that position sticky can only be applied to the children which belongs to a scrollable parent with a known height, (a direct children in a flexbox has a know height which is computed from other flex-item) – code4j Apr 13 '19 at 14:11
  • @ViliusL thank you for your comment! That was the problem I had with position: sticky, I wouldn't have known that if not for you as I didn't find this information anywhere. – Piotr Szlagura Jan 24 '20 at 16:31
  • sticky position should not have a parent with overflow property, also sticky is not supported in some web browsers – Rohan Shenoy Jan 29 '20 at 06:04
  • Hey @ViliusL, Thanks! man. Your comment saved a whole lot of headache and helped a lot more than the answer itself. LOL – Debu Shinobi Jan 09 '21 at 08:11
  • I had a similar problem because the parent had `height; 100vh` and that doesn't work with sticky on child (Chrome 89 - Ubuntu) – Javier Dottori Mar 21 '21 at 03:41
  • "Parent also should not have overflow property," this is not quite correct. More precisely, only the scrollable parent should have an overflow property and no other elements in between. – graup Jan 17 '22 at 13:23
188

I have same problem, and i found the answer here.

If your element isn't sticking as expected the first thing to check are the rules applied to the container.

Specifically, look for any overflow property set on any parents of the element. You can't use: overflow: hidden, overflow: scroll or overflow: auto on the parent of a position: sticky element.

Brett Jackson
  • 617
  • 2
  • 9
  • 22
Miftah Mizwar
  • 3,722
  • 2
  • 13
  • 20
  • This is also the right answer but also see my answer which helps to easily identify the parent causing the problem. – danday74 Jul 07 '18 at 10:09
  • 46
    You should check not only closest parent container but all parent element**s**. – Mariusz Pawelski Jul 24 '18 at 16:58
  • yes, see my answer to do exactly that and check ALL parents very quickly – danday74 Jul 28 '18 at 10:12
  • 1
    The link helped me, too. My problem was solved by "...If you're not using overflow and still having problems it's worth checking if a height is set on the parent..." – lmiller1990 Oct 18 '18 at 02:23
  • Life saver, really simple description to the problem , but is there a work around to this just curious – Rahul Singh Aug 21 '19 at 18:43
  • 1
    I have checked all parents going up and non of them uses overflow attribute. There must be something else. – Athlan Aug 22 '20 at 18:15
  • 2
    Note: Unfortunately `overflow-x` also breaks even for a vertically positioned sticky item. – Simon_Weaver Aug 03 '21 at 06:20
  • 10
    The example of `position: sticky` in the [API](https://developer.mozilla.org/en-US/docs/Web/CSS/position) is literally an element inside a `overflow: auto`-container. Isn't the whole point of `position: sticky` to live inside scroll containers? This answer confuses me so much. – blid Aug 11 '21 at 09:25
  • Yes! this solved my problem. can you tell me the reason? – Mahdi-Jafaree Oct 05 '21 at 14:30
  • 1
    overflow on parent wouldn't matter if you just add `display: initial` to it (the parent). – Sumit Wadhwa Oct 23 '21 at 10:20
  • I think [@blid](https://stackoverflow.com/users/8134642/blid) gave a good example as to how position sticky actually REQUIRES a parent element that has a known height and is scrollable, like demonstrated in this Stackblitz: https://stackblitz.com/edit/position-sticky-example?file=index.html . **You can see that it works and when you try to remove `height: 100%;` and `overflow: auto;` from `.page-content` it will still work. It won't work if you try to remove instead only the height property. Tested in Chrome version 100.0.4896.127 on Mac** – Razvan-Catalin Olaru Apr 26 '22 at 20:15
104

Incase you came across this and your sticky is not working - try setting the parent to:

display: unset

Worked for me

eaglebearer
  • 2,060
  • 1
  • 13
  • 9
  • 2
    Oh! This seems to be a work-around for the case that a parent element has a limited height (e.g. a navbar container or something, as opposed to a whole body or page container) - it seems that stickies don't like to scroll outside their parents (which is utterly ridiculous!) Now everybody just needs to be so lucky as to be able to set the parent to `display: unset`, which may not always be immediately viable – Ben Philipp Jul 01 '20 at 19:38
  • 2
    Also seems to work with `contents`, `initial`(?) and `inline` – Ben Philipp Jul 01 '20 at 19:44
  • it works. But why does it work.? – Sumit Wadhwa Oct 23 '21 at 10:02
  • `display: initial;` also works. – Sumit Wadhwa Oct 23 '21 at 10:24
  • Assuming this "works" because the `overflow` property does not apply to inline elements (which for some reason is what `div` elements are when set to `display: initial` (or `unset`) – b4tch Apr 06 '22 at 11:50
51

Few more things I've come across:

When your sticky element is a component (angular etc)

  • If the 'sticky' element itself is a component with a custom element-selector, such as an angular component named <app-menu-bar> you will need to add the following to the component's css:

      :host { display: block; }     // or use flexbox
    

or

    app-menu-bar  { display: block; }   // (in the containing component's css)

Safari on iOS in particular seems to require `display:block` even on the root element `app-root` of an angular application or it won't stick.
  • If you are creating a component and defining the css inside the component (shadow DOM / encapsulated styles), make sure the position: sticky is being applied to the 'outer' selector (eg. app-menu-bar in devtools should show the sticky position) and not a top level div within the component. With Angular, this can be achieved with the :host selector in the css for your component.

      :host
      {
          position: sticky;
          display: block;   // this is the same as shown above
          top: 0;
          background: red;    
      }
    

Other

  • If the element following your sticky element has a solid background, you must add the following to stop it from sliding underneath:

      .sticky-element { z-index: 100; }
      .parent-of-sticky-element { position: relative; }
    
  • Your sticky element must be before your content if using top and after it if using bottom.

  • There are complications when using overflow: hidden on your wrapper element – in general it will kill the sticky element inside. Better explained in this question

  • Mobile browsers may disable sticky/fixed positioned items when the onscreen keyboard is visible. I'm not sure of the exact rules (does anybody ever know) but when the keyboard is visible you're looking at a sort of 'window' into the window and you won't easily be able to get things to stick to the actual visible top of the screen.

  • Make sure you have:

    position: sticky;

    and not

    display: sticky;

Misc usability concerns

  • Be cautious if your design calls for for sticking things to the bottom of the screen on mobile devices. On iPhone X for instance they display a narrow line to indicate the swipe region (to get back to the homepage) - and elements inside this region aren't clickable. So if you stick something there be sure to test on iPhone X that users can activate it. A big 'Buy Now' button is no good if people can't click it!
  • If you're advertising on Facebook the webpage is displayed in a 'webview' control within Facebook's mobile apps. Especially when displaying video (where your content begins in the bottom half of the screen only) - they often completely mess up sticky elements by putting your page within a scrollable viewport that actually allows your sticky elements to disappear off the top of the page. Be sure to test in the context of an actual ad and not just in the phone's browser or even Facebook's browser which can all behave differently.
Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
  • @Sergey I'm not absolutely clear which of `display: block` and `position: relative` triggers the correct behavior in Safari - did it seem that only `display: block` was needed? Of course it probably doesn't hurt to have both specified – Simon_Weaver Jun 28 '18 at 20:06
  • 3
    Just `display: block` was enough – Sergey Jun 28 '18 at 20:30
  • I've probably referred back to this answer more than any other of my own. This time it was overflow: hidden. – Simon_Weaver Sep 07 '22 at 01:37
36

This is a continuation of the answers from MarsAndBack and Miftah Mizwar.

Their answers are correct. However, it is difficult to identify the problem ancestor(s).

To make this very simple, simply run this jQuery script in your browser console and it will tell you the value of the overflow property on every ancestor.

$('.your-sticky-element').parents().filter(function() {
    console.log($(this));
    console.log($(this).css('overflow'));
    return $(this).css('overflow') === 'hidden';
});

Where an ancestor does not have overflow: visible change its CSS so that it does!

Also, as stated elsewhere, make sure your sticky element has this in the CSS:

.your-sticky-element {
    position: sticky;
    top: 0;
}
Asons
  • 84,923
  • 12
  • 110
  • 165
danday74
  • 52,471
  • 49
  • 232
  • 283
  • 2
    That was very helpful. I was able to quickly identify the parent element that had a value of ```overflow: invisible```. – David Brower Sep 25 '18 at 21:01
  • 6
    If you don't have jQuery on your page, you can run this small snippet in the console to add it: `(async function() { let response = await fetch('https://code.jquery.com/jquery-3.3.1.min.js'); let script = await response.text(); eval(script); })();` – magikMaker Nov 05 '18 at 16:51
  • 14
    You can also run this snippet that doesn't require JQuery to find non "visible" overflow parent: `var p = $0.parentElement; while(p != null) { var ov = getComputedStyle(p).overflow; if(ov !== 'visible') console.warn(ov, p); else console.log(ov, p); p = p.parentElement; }` Where `$0` is the [last element](https://developers.google.com/web/tools/chrome-devtools/console/utilities?utm_source=dcc&utm_medium=redirect&utm_campaign=2016q3#dom) selected in developer tools. – Mariusz Pawelski Jan 02 '19 at 15:54
  • One could as well handle `position: fixed` and appropriate properties with JavaScript. Question doesn't state the need for JavaScript. – vintprox Jul 19 '20 at 09:49
25

I had to use the following CSS to get it working:

.parent {
    display: flex;
    justify-content: space-around;
    align-items: flex-start;
    overflow: visible;
}

.sticky {
    position: sticky;
    position: -webkit-sticky;
    top: 0;
}

If above dosen't work then...

Go through all ancestors and make sure none of these elements have overflow: hidden. You have to change this to overflow: visible

Unicco
  • 2,466
  • 1
  • 26
  • 30
24

Another very common scenario where position: sticky might not be working is if it's parent has display: flex or display: grid.

What happens in this case is the sticky position is working but you can't see it bcoz the element is stretched completely. Try reducing it's height using align-self: baseline and you'll see the effect.

Badmaash
  • 683
  • 7
  • 17
23

Sticky Position will not work if your parent is using display flex. As I read this in one solution

Since flex box elements default to stretch, all the elements are the same height, which can't be scrolled against.

So if you are using display: flex; on parent then you will have to add this to sticky element align-self: flex-start; and also set height to auto height: auto;

This is how sticky element class look like

.stick-ontop {
  position: -webkit-sticky !important; // for safari
  position: sticky !important;
  top: 0;
  align-self: flex-start;
  height: auto;
}
Zohab Ali
  • 8,426
  • 4
  • 55
  • 63
18

Attack this Q from other direction.

Imagine this is a game Find the nearest scrolling ancestor.

<!-- sticky not working -->
<h1 style="position: sticky; top:0;">Hello World</h1>

Questions:

  • 1/3: The sticky node? <h1>.
  • 2/3: The ancestor? <body>.
  • 3/3: <body> scrolling ? FALSE => "No effect".

Fix: "Sticky is working" (<body> scrolling ? TRUE).

body{
  min-height: 300vh;
}
<!-- sticky working -->
<h1 style="position: sticky; top: 0;">Hello World</h1>

With this in mind - here are some "hello world" "famous" scenarios of "not working" sticky :) Most cases relate to one or many of these cases.

Case 1: Missing "top" (Easy to fix):

Not working:

/* not working example */
aside{
  position: sticky;
  background: lightgray;
}

main{
  height: 200vh;
}
<aside>
  <h2>sticky Aside</h2>
</aside>

<main>
  <h1>Article</h1>
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
  </p>
</main>

Fix (Add top):

aside{
  position: sticky;
  top: 0;
}

main{
  height: 200vh;
}
<aside>
  <h2>sticky Aside</h2>
</aside>

<main>
  <h1>Article</h1>
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
  </p>
</main>

Case 2: Sticky node & overflow (Easy to fix):

I "destroy" the sticky by adding #extra-wrapper with overflow setting auto -or- hidden -or- visible - but without any clipped content.

"The problem" now the nearest scrolling ancestor (#extra-wrapper) "without" any scrolling (No scrollbar dragging option == "no scrolling ancestor").

Not working:

/* not working example */
#overflow-wrapper{
  overflow: scroll;
}

aside{
  position: sticky;
  background: lightgray;
  top: 0px;
}

main{
  height: 200vh;
}
<div id="overflow-wrapper">
  <aside>
    <h2>sticky Aside</h2>
  </aside>

  <main>
    <h1>Article</h1>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
    </p>
  </main>
</div>

Fix - Clip the content (Now their is "nearest scrolling ancestor").

Working:

/* not working example */
#overflow-wrapper{
  overflow: scroll;
  max-height: 60vh; /* clip the content */
}

aside{
  position: sticky;
  background: lightgray;
  top: 0px;
}

main{
  height: 200vh;
}
<div id="overflow-wrapper">
  <aside>
    <h2>sticky Aside</h2>
  </aside>

  <main>
    <h1>Article</h1>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
    </p>
  </main>
</div>

Case 3: Sticky related to "wrong / not scrolling" node (Tricky to fix)

Again, Sticky offset relative to its nearest scrolling ancestor.

I "destroy" the sticky by adding #extra-wrapper to the sticky element. Why it is not working? Now the height of #extra-wrapper == height aside content (box model) == "no scrolling ancestor" == "no effect".

Not working:

/* not working example */
aside{
  position: sticky;
  top: 0;
  background: lightgray;
}

main{
  height: 200vh;
}
<div id="extra-wrapper">
  <aside>
    <h2>sticky Aside</h2>
  </aside>
</div>

<main>
  <h1>Article</h1>
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
  </p>
</main>

This is what really "happens" (I added height to #extra-wrapper):

#extra-wrapper{
  background: lightgray;
  height: 40vh;
}
aside{
  position: sticky;
  top: 0;
}

main{
  height: 200vh;
}
<div id="extra-wrapper">
  <aside>
    <h2>sticky Aside</h2>
  </aside>
</div>

<main>
  <h1>Article</h1>
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
  </p>
</main>

FIX: change the sticky node:

#extra-wrapper{
  position: sticky;
  top: 0;
}
aside{

}

#layout{
  displ
}
main{
height: 200vh;
}
<div id="extra-wrapper">
  <aside>
    <h2>sticky Aside</h2>
  </aside>
</div>

<main>
  <h1>Article</h1>
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
  </p>
</main>

Case 4: display: flexbox/Grid layout - even cols by deafult (Tricky to fix)

You create flex/grid layout & set one of the cols to be sticky. By default the cols height is even = The height of the "nearest ancestor" (wrapper) == Cols height = no scroll effect.

Not working:

#extra-wrapper{
  position: sticky;
  top: 0;
  border: 1px solid red;
}
aside{

}

#layout{
  display: flex;
}
main{
height: 200vh;
}
<div id="layout">
  <div id="extra-wrapper">
    <aside>
      <h2>sticky Aside</h2>
    </aside>
  </div>

  <main>
    <h1>Article</h1>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
    </p>
  </main>
</div>

FIX: Set the sticky aside max-height to be 90vh for example (Now the cols height is not even).

Working:

#extra-wrapper{
  position: sticky;
  top: 0;
  border: 1px solid red;
  max-height: 90vh;
}
aside{

}

#layout{
  display: flex;
}
main{
height: 200vh;
}
<div id="layout">
  <div id="extra-wrapper">
    <aside>
      <h2>sticky Aside</h2>
    </aside>
  </div>

  <main>
    <h1>Article</h1>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
    </p>
  </main>
</div>
Ezra Siton
  • 6,887
  • 2
  • 25
  • 37
8

It seems like that the navbar to be stickied shouldn't be inside any div or section with other content. None of the solution were working for me until I took the navbar out of the div which the navbar shared with another topbar .I previously had topbar and navbar wrapped with a common div.

solaris333
  • 81
  • 1
  • 2
  • 1
    Thank you, this was what I was looking for! For me there were no parents with overflow, nor were there any height issues. It'd be nice to see this info in the accepted answer as well. – saglamcem Feb 18 '19 at 14:20
  • I just found out through experimenting that the sticky won't scroll outside its parent element. It doesn't so much matter how many other siblings it has, but its parent can't, apparently, be a navbar container but has to be something on tle level of a page/content container :( This is ridiculous – Ben Philipp Jul 01 '20 at 19:33
  • But Lo! If you can, if you set the limiting parent to `display: unset`, this restriction is lifted! see https://stackoverflow.com/a/60560997/2902367 Also seems to work with `contents`, `initial`(?) and `inline` – Ben Philipp Jul 01 '20 at 19:40
  • 1
    Here is a useful article explaining this behavior in more detail - https://medium.com/@elad/css-position-sticky-how-it-really-works-54cd01dc2d46 – Yangshun Tay Oct 13 '20 at 10:52
8

I know this seems to be already answered, but I ran into a specific case, and I feel most answers miss the point.

The overflow:hidden answers cover 90% of the cases. That's more or less the "sticky nav" scenario.

But the sticky behavior is best used within the height of a container. Think of a newsletter form in the right column of your website that scrolls down with the page. If your sticky element is the only child of the container, the container is the exact same size, and there's no room to scroll.

Your container needs to be the height you expect your element to scroll within. Which in my "right column" scenario is the height of the left column. The best way to achieve this is to use display:table-cell on the columns. If you can't, and are stuck with float:right and such like I was, you'll have to either guess the left column height of compute it with Javascript.

Tristan M
  • 133
  • 1
  • 3
  • 4
    "If your sticky element is the only child of the container, the container is the exact same size, and there's no room to scroll." GOLD!!! Thank you! – remjx Mar 12 '20 at 02:32
  • 1
    Yes! Took me a while to figure this one out. I found it a ridiculous restriction at first, but I suppose there should be at least the _option_ for that. I'd much prefer something like `sticky-limit: parent`, `sticky-limit: body` or `sticky-limit: nth-parent(3)`... Anyway: If you can, you can lift this limitation by setting the limiting parent to `display: unset`, as noted in https://stackoverflow.com/a/60560997/2902367 . Also seems to work with `contents`, `initial`(?) and `inline` – Ben Philipp Jul 01 '20 at 19:49
6

I know it's too late. But I found a solution even if you are using overflow or display:flex in parent elements sticky will work.

steps:

  1. Create a parent element for the element you want to set sticky (Get sure that the created element is relative to body or to full-width & full-height parent).

  2. Add the following styles to the parent element:

    { position: absolute; height: 100vmax; }

  3. For the sticky element, get sure to add z-index that is higher than all elements in the page.

That's it! Now it must work. Regards

mohamad b
  • 81
  • 2
  • 4
5

from my comment:

position:sticky needs a coordonate to tel where to stick

nav {
  position: sticky;
  top: 0;
}

.nav-selections {
  text-transform: uppercase;
  letter-spacing: 5px;
  font: 18px "lato", sans-serif;
  display: inline-block;
  text-decoration: none;
  color: white;
  padding: 18px;
  float: right;
  margin-left: 50px;
  transition: 1.5s;
}

.nav-selections:hover {
  transition: 1.5s;
  color: black;
}

ul {
  background-color: #B79b58;
  overflow: auto;
}

li {
  list-style-type: none;
}

body {
  height: 200vh;
}
<nav>
  <ul align="left">
    <li><a href="#/contact" class="nav-selections" style="margin-right:35px;">Contact</a></li>
    <li><a href="#/about" class="nav-selections">About</a></li>
    <li><a href="#/products" class="nav-selections">Products</a></li>
    <li><a href="#" class="nav-selections">Home</a></li>
  </ul>
</nav>

There is polyfill to use for other browsers than FF and Chrome . This is an experimental rules that can be implemented or not at any time through browsers. Chrome add it a couple of years ago and then dropped it, it seems back ... but for how long ?

The closest would be position:relative + coordonates updated while scrolling once reached the sticky point, if you want to turn this into a javascript script

G-Cyrillus
  • 101,410
  • 14
  • 105
  • 129
5

Funny moment that wasn't obvious for me: at least in Chrome 70 position: sticky is not applied if you've set it using DevTools.

i.k
  • 87
  • 1
  • 4
5

I know this is an old post. But if there's someone like me that just recently started messing around with position: sticky this can be useful.

In my case i was using position: sticky as a grid-item. It was not working and the problem was an overflow-x: hidden on the html element. As soon as i removed that property it worked fine. Having overflow-x: hidden on the body element seemed to work tho, no idea why yet.

Marco Peralta
  • 79
  • 1
  • 3
5

1.Position sticky will most probably not work if overflow is set to hidden, scroll, or auto on any of the parents of the element.

2.Position sticky may not work correctly if any parent element has a set height.

NimaDoustdar
  • 318
  • 3
  • 5
  • The question was "how does it work" and not "how does it not work" :) Please, add some solutions or spare them out. You can also add comments to the questions, instead of adding "Answers" which are not answers. – Michael W. Czechowski Sep 07 '21 at 12:38
  • I find the "how does it not work" question to be more legit in this case, since the "how it does work" is pretty simple and you need to find out in most cases what went wrong, thus that question – Benzara Tahar May 11 '22 at 18:54
  • This answer completely contradicts the API docs for how sticky position operates: https://developer.mozilla.org/en-US/docs/Web/CSS/position "Note that a sticky element "sticks" to its nearest ancestor that has a "scrolling mechanism" (created when overflow is hidden, scroll, auto, or overlay), even if that ancestor isn't the nearest actually scrolling ancestor." Literally sticks to an overflow element. – lux Aug 02 '23 at 21:32
3

I believe this article say a lot about how sticky works

How CSS Position Sticky Really Works! CSS position sticky has two main parts, sticky item & sticky container.

Sticky Item — is the element that we defined with the position: sticky styles. The element will float when the viewport position matches the position definition, for example: top: 0px .

Sticky Container —is the HTML element which wraps the sticky item. This is the maximum area that the sticky item can float in.

When you define an element with position: sticky you’re automatically defining the parent element as a sticky container!

yuval.bl
  • 4,890
  • 3
  • 17
  • 31
2

two answer here:

  1. remove overflow property from body tag

  2. set height: 100% to the body to fix the problem with overflow-y: auto

min-height: 100% not-working instead of height: 100%

Javad Ebrahimi
  • 333
  • 5
  • 13
2

z-index is also very important. Sometimes it will work but you just won't see it. Try setting it to some very high number just to be sure. Also don't always put top: 0 but try something higher in case it's hidden somewhere (under a toolbar).

M1X
  • 4,971
  • 10
  • 61
  • 123
2

The real behavior of a sticky element is:

  • First it is relative for a while
  • then it is fixed for a while
  • finally, it disappears from the view

A stickily positioned element is treated as relatively positioned until its containing block crosses a specified threshold (such as setting top to value other than auto) within its flow root (or the container it scrolls within), at which point it is treated as "stuck" until meeting the opposite edge of its containing block.

The element is positioned according to the normal flow of the document, and then offset relative to its nearest scrolling ancestor and containing block (nearest block-level ancestor), including table-related elements, based on the values of top, right, bottom, and left. The offset does not affect the position of any other elements.

This value always creates a new stacking context. Note that a sticky element "sticks" to its nearest ancestor that has a "scrolling mechanism" (created when overflow is hidden, scroll, auto, or overlay), even if that ancestor isn't the nearest actually scrolling ancestor.

This example will help you understand:

code https://codepen.io/darylljann/pen/PpjwPM

Juanma Menendez
  • 17,253
  • 7
  • 59
  • 56
2

Using the strategy from this blog (https://www.designcise.com/web/tutorial/how-to-fix-issues-with-css-position-sticky-not-working) I came up with an option for those that can't have control over all components in the page

I'm using Angular and in the ngOnInit method I run this code to change the visible propertys of parents to visible

/**
 * position: sticky
 * only works if all parent components are visibile
 */
let parent = document.querySelector('.sticky').parentElement;
while (parent) {
  const hasOverflow = getComputedStyle(parent).overflow;
  if (hasOverflow !== 'visible') {
    parent.style.overflow = 'visible';
    // console.log(hasOverflow, parent);
  }
  parent = parent.parentElement;
}
Mr. T
  • 51
  • 4
2

If any parent/ancestor of the sticky element has any of the following overflow properties set, position: sticky won't work (unless you specify a height on the overflowing container):

  • overflow: hidden
  • overflow: scroll
  • overflow: auto

Snippet to Check for Parents With overflow Property Set:

let parent = document.querySelector('.sticky').parentElement;

while (parent) {
    const hasOverflow = getComputedStyle(parent).overflow;
    if (hasOverflow !== 'visible') {
        console.log(hasOverflow, parent);
    }
    parent = parent.parentElement;
}

There are other methods in this article, but this one worked for me, I used an animation library and had the parent element add the overflow: hidden property without my knowledge, causing sticky to not work.

Quote orgin: https://www.designcise.com/web/tutorial/how-to-fix-issues-with-css-position-sticky-not-working

Dong
  • 41
  • 5
2

Just adding my two cents after facing this issue: I was adding position sticky to a sidebar with a flex wrapper.

  1. adding position: sticky is not enough: it needs to top or bottom with a value different than auto to work
  2. position: sticky; top: 0; wasn't enough, I used to have a overflow: hidden on a parent (well, a parent of a parent of the parent of my sidebar to be more accurate)
  3. position: sticky; top: 0; and removing the problematic overflow: hidden wasn't enough: my flex container needed a align-items: flex-start. It makes sense, in a flex context, a flex item that is not stacked in the top of its parent shouldn't behave as a sticky element.

It's just a summary of all other answers.

enguerranws
  • 8,087
  • 8
  • 49
  • 97
1

Watch out for empty grid areas!

In my case I had something like this:

.cart-areas {
    grid-template-columns: 2fr 0.2fr 1.5fr;
    grid-template-areas:
      'order . summary'
      'order . .'
      'order . .';
}

I wanted that summary grid item to be sticky when the user completes the checkout form. It didn't work because of those empty grid items (marked with .).

The solution was to delete those empty items like so:

.cart-areas {
    grid-template-columns: 2fr 0.2fr 1.5fr;
    grid-template-areas:
      'order . summary'
      'order . summary'
      'order . summary';
}
Gabriel Arghire
  • 1,992
  • 1
  • 21
  • 34
1

Just to add to all the answers on this page, if you're having problems with position: sticky not working, then you can check for the following:

  1. Browser support
  2. If threshold is specified — i.e. top, left, bottom or right must be set to a value other than auto
  3. Vendor prefix for Safari/iOS v13 and below — i.e. position: -webkit-sticky
  4. Ancestor element with overflow property must have a height
  5. Parent element must have a height specified (for sticky to have area to scroll within)
  6. In a flexbox, change align-self value if sticky has align-self: auto or align-self: stretch set

Wrote a blog post for those who are interested in learning more.

designcise
  • 4,204
  • 1
  • 17
  • 13
0

if danday74's fix doesn't work, check that the parent element has a height.

In my case I had two childs, one floating left and one floating right. I wanted the right floating one to become sticky but had to add a <div style="clear: both;"></div> at the end of the parent, to give it height.

Flo
  • 139
  • 1
  • 4
  • Unsure why this got downvoted by someone, but this was my case exactly: I added `float:left` to the parent element (without float it's height was 0) and `position:sticky` worked. – ᴍᴇʜᴏᴠ Mar 13 '20 at 13:21
0

I used a JS solution. It works in Firefox and Chrome. Any problems, let me know.

html

<body>
  <header id="header">
    <h1>Extra-Long Page Heading That Wraps</h1>
    <nav id="nav">
      <ul>
        <li><a href="" title="">Home</a></li>
        <li><a href="" title="">Page 2</a></li>
        <li><a href="" title="">Page 3</a></li>
      </ul>
    </nav>
  </header>
  <main>
    <p><!-- ridiculously long content --></p>
  </main>
  <footer>
    <p>FOOTER CONTENT</p>
  </footer>
  <script src="navbar.js" type="text/javascript"></script>
</body>

css

nav a {
    background: #aaa;
    font-size: 1.2rem;
    text-decoration: none;
    padding: 10px;
}

nav a:hover {
    background: #bbb;
}

nav li {
    background: #aaa;
    padding: 10px 0;

}

nav ul  {
    background: #aaa;
    list-style-type: none;
    margin: 0;
    padding: 0;

}

@media (min-width: 768px) {

    nav ul {
        display: flex;
    }
}

js

function applyNavbarSticky() {
    let header = document.querySelector('body > header:first-child')
    let navbar = document.querySelector('nav')
    header.style.position = 'sticky'

    function setTop() {
        let headerHeight = header.clientHeight
        let navbarHeight = navbar.clientHeight
        let styleTop = navbarHeight - headerHeight

        header.style.top = `${styleTop}px`
    }

    setTop()

    window.onresize = function () {
        setTop()
    }
}
Carl Brubaker
  • 1,602
  • 11
  • 26
0

Here's what was tripping ME up... my sticky div was inside another div so that parent div needed some additional content AFTER the sticky div, to make the parent div "tall enough" for the sticky div to "slide over" other content as you scroll down.

So in my case, right after the sticky div, I had to add:

    %div{style:"height:600px;"} 
      &nbsp;

(My application has two side-by-side divs, with a "tall" image on the left, and a short data entry form on the right, and I wanted the data entry form to float next to the image as you scroll down, so the form is always on the screen. It would not work until I added the above "extra content" so the sticky div has something to "slide over"

jpw
  • 18,697
  • 25
  • 111
  • 187
0

I had the same problem. For me the problem was display: 'none' on big screens (media-query) and display: 'initial' on smartphones. If i removed the display css property and added opacity and pointer events none on desktop everything workedout.

Simon Ludwig
  • 1,754
  • 1
  • 20
  • 27
0

Despite reading this entire page and trying everything but the kitchen sink this simply doesn't work on my mobile device. No sticky action at all -- and that's the only place I need it to work. I have ruled out any special designation or overrides that would stop this from functioning for this screen width. Cleared my mobile formatting code as a test and no change was seen. Works perfectly on Chrome browser on my laptop and not at all on chrome browser on my new S10.

user3035649
  • 472
  • 1
  • 4
  • 12
0

Imagine that you want to maintain the overflow: hidden for your ancestor and have all the goodies from the position: sticky, then use contain in replacement of overflow:

contain: paint;

And position: sticky should still work as expected

M.A Shahbazi
  • 876
  • 9
  • 23
-1

It's TRUE that the overflow needs to be removed or set to initial to make position: sticky works on the child element. I used Material Design in my Angular app and found out that some Material components changed the overflow value. The fix for my scenario is

mat-sidenav-container, mat-sidenav-content {
  overflow: initial;
}
Anh Nguyen
  • 1,202
  • 14
  • 17
-1

One of the most common mistakes when going about the position sticky is:

  1. Browser not supporting it
  2. Not including any top bottom right left properties. Bare in mind that the Browser won't know how to handle that properly if you don't give it enough information. It will be just be statically positioned without it.

I have written about that and more insides on this article. Just putting a reference so I don't repeat myself too much.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
Jose Greinch
  • 394
  • 2
  • 13
-1

It doesn't work if parent has float: property.

luky
  • 2,263
  • 3
  • 22
  • 40
-1

For position: sticky; to work your sticky element should be placed right in body tag as a direct child and should not have any wrapper divs or anything like that. Because it can stay fixed only till it gets to the bottom of the parent element. And you should specify where it should stick to like: top: 0;

-3

// Sticky Code

const stickyItem = document.querySelector(".item-to-made-sticky")

document.addEventListener("scroll", () => { stickyItem.style.transform = translateY(${window.scrollY}px) })

Nawab Ali
  • 176
  • 2
  • 7