Search code examples
cssflexboxsticky

Nested flexboxes and position:sticky for top&bottom?


I'm trying to get a column of flexboxes to stick their section headers to the top and bottom, and so far I've only been able to get the top ones to stick appropriately.

I've read a whole bunch of docs saying flexboxes, overflow, and pretty much everything else I'm using don't work nicely with position: fixed, however empirically the top: 0 works just fine.

Is there something I'm missing to make the layout work correctly for both top and bottom sticky headers?

Codepen: https://codepen.io/turt2live/pen/bGEebve

const list = document.querySelector(".actualList");
for (let i = 0; i < 20; i++) {
  const sublist = document.createElement("div");
  sublist.classList.add("sublist");
  
  const name = document.createElement("div");
  name.classList.add("name");
  name.innerText = `Test ${i + 1}`;
  sublist.appendChild(name);
  
  const rooms = document.createElement("div");
  rooms.classList.add("rooms");
  for (let j = 0; j < 4; j++) {
    const room = document.createElement("div");
    room.classList.add("room");
    room.innerText = `Room ${j + 1}`;
    rooms.appendChild(room);
  }
  sublist.appendChild(rooms);
  
  list.appendChild(sublist);
}
body, html {
  height: 100vh;
  margin: 0;
}

.body {
  height: 100vh;
}

.wrapper {
  display: flex;
  width: 100%;
  height: 100vh;
}

.chat {
  background-color: aliceblue;
  width: 100%;
  height: 100vh;
  display: flex;
}

.left {
  width: 300px;
  height: 100vh;
  background-color: pink;
  display: flex;
  flex-direction: column;
}

.header {
  border-bottom: 1px solid red;
  width: 100%;
  height: 37px;
}

.list {
  overflow: auto;
  display: flex;
}

.actualList {
  position: relative;
  width: 100%;
  flex: 1;
  display: flex;
  flex-direction: column;
}

.sublist {
  width: 100%;
  display: flex;
  flex-direction: column;
}

.name {
  position: sticky;
  bottom: 0; /* This doesn't appear to be working */
  top: 0;
  margin: auto;
  background-color: coral;
  width: 100%;
}

.room {
  padding-left: 10px;
}
<div class="body">
  <div class="wrapper">
    <div class="chat">
      <div class="left">
        <div class="header">
          <b>Alice</b>
          <div>Menu options</div>
        </div>
        <div class="list">
          <div class="actualList">
            <!-- generated -->
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

(I've mocked out the entire dom structure for the app to be sure it's not one of the other parents causing problems). For clarity: I'm expecting to be able to get the Test N headers to stick to the top and bottom of the container they're in.


Solution

  • All your sticky elements need to belong to the same container to get this effect. So either remove sublist containers or use display:contents on them.

    You need to remove a bunch of useless code. Also note that having bottom sticky for all your elements will only make the last one to be visible as it will be above all the others:

    const list = document.querySelector(".actualList");
    for (let i = 0; i < 20; i++) {
      const sublist = document.createElement("div");
      sublist.classList.add("sublist");
      
      const name = document.createElement("div");
      name.classList.add("name");
      name.innerText = `Test ${i + 1}`;
      sublist.appendChild(name);
      
      const rooms = document.createElement("div");
      rooms.classList.add("rooms");
      for (let j = 0; j < 4; j++) {
        const room = document.createElement("div");
        room.classList.add("room");
        room.innerText = `Room ${j + 1}`;
        rooms.appendChild(room);
      }
      sublist.appendChild(rooms);
      
      list.appendChild(sublist);
    }
    body, html {
      margin: 0;
    }
    
    .wrapper {
      height: 100vh;
      overflow: auto;
    }
    
    .chat {
      background-color: aliceblue;
      height: 100%;
    }
    
    .left {
      width: 300px;
      background-color: pink;   
      display: flex;
      flex-direction: column;
      height: 100%;
    }
    
    .header {
      border-bottom: 1px solid red;
      height: 37px;
    }
    
    .list {
      overflow: auto;
    }
    
    .actualList {
      flex: 1;
      display: flex;
      flex-direction: column;
    }
    
    .sublist {
      display: contents;
    }
    
    .name {
      position: sticky;
      bottom: 0; /* This doesn't appear to be working */
      top: 0;
      margin: auto;
      background-color: coral;
      width: 100%;
    }
    
    .room {
      padding-left: 10px;
    }
    <div class="wrapper">
        <div class="chat">
          <div class="left">
            <div class="header">
              <b>Alice</b>
              <div>Menu options</div>
            </div>
            <div class="list">
              <div class="actualList">
                <!-- generated -->
              </div>
            </div>
          </div>
        </div>
      </div>

    Related questions for more details:

    Why bottom:0 doesn't work with position:sticky?

    If you specify `bottom: 0` for position: sticky, why is it doing something different from the specs?