4

I'm trying to create a layout that uses a grid that takes up the entire browser viewport (height: 100vh) and then allows a div within one of the grid items to scroll in the Y dimension.

Here's some example code. There are three grid areas: a header across the top, a left nav, and a content area in the lower right.

I'm trying to make it so div inside the content area (the big-list), is the only thing with a vertical scrollbar, and the rest of the UI stays on the screen.

body {
    margin: 0;
}
.outer-grid {
    display: grid;
    height: 100vh;
    grid-template-columns: 180px 1fr;
    grid-template-rows: min-content 1fr;
}
.top-bar {
    grid-column-start: 1;
    grid-column-end: 3;
    border-bottom: 1px solid rgb(200, 200,200);
}
.header {
    font-weight: bold;
    margin: 8px;
}
.left-nav {
    overflow-y: scroll;
    padding: 8px;
    border-right: 1px solid rgb(200, 200, 200);
}
#content {
    padding-left: 8px;
    padding-right: 8px;
}
#search {
    width:100%;
}
.big-list {
    overflow-y: scroll;
}
.big-list div {
    padding: 8px 0;
    border-top: 1px solid rgb(230,230,230);
}
<body>
  <div class="outer-grid">

    <div class="top-bar">
      <div class="header">Header</div>
    </div>

    <div class="left-nav">
      <div>Nav 1</div>
      <div>Nav 2</div>
      <div>Nav 3</div>        
    </div>

    <div id="content">
      <input id='search' type="text"/>

      <div class='big-list'>
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Last Row </div>   
      </div>
    </div>
  </div>
</body>
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
Rob N
  • 15,024
  • 17
  • 92
  • 165

2 Answers2

8

Solution

Add this to your code:

#content {
  display: flex;          /* new */
  flex-direction: column; /* new */
}

.big-list {
  flex: 1 0 1px;          /* new */
}

.outer-grid {
  display: grid;
  height: 100vh;
  grid-template-columns: 180px 1fr;
  grid-template-rows: min-content 1fr;
}

.top-bar {
  grid-column-start: 1;
  grid-column-end: 3;
  border-bottom: 1px solid rgb(200, 200, 200);
}

.header {
  font-weight: bold;
  margin: 8px;
}

.left-nav {
  /* overflow-y: scroll; */ /* removed */
  padding: 8px;
  border-right: 1px solid rgb(200, 200, 200);
}

#content {
  display: flex;            /* new */
  flex-direction: column;   /* new */
  padding-left: 8px;
  padding-right: 8px;
}

#search {
  width: 100%;
}

.big-list {
  flex: 1 0 1px;             /* new */
  overflow-y: auto;          /* adjusted */
}

.big-list div {
  padding: 8px 0;
  border-top: 1px solid rgb(230, 230, 230);
}

body {
  margin: 0;
}
<div class="outer-grid">
  <div class="top-bar">
    <div class="header">Header</div>
  </div>
  <div class="left-nav">
    <div>Nav 1</div>
    <div>Nav 2</div>
    <div>Nav 3</div>
  </div>
  <div id="content">
    <input id='search' type="text" />
    <div class='big-list'>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Row ... </div>
      <div> Last Row </div>
    </div>
  </div>
</div>

Explanation

For an overflow to occur, content must overflow the container.

For content to overflow the container, the container must have a size limitation. Usually it's a fixed or maximum length, such as width: 150px or max-height: 50vh.

From MDN:

In order for overflow to have an effect, the block-level container must have either a set height (height or max-height) or white-space set to nowrap.

You're trying to render a vertical scrollbar on your list element (.big-list), but it doesn't have a height limitation.

The heights you have in place for it are:

  1. 1fr (for the row, set by .outer-container). This isn't even a length. It's a distribution of free space. This cannot trigger an overflow.

  2. height: auto, which is the default height on elements. This doesn't provide any limitation, as content will simply keep expanding the container. So this cannot trigger an overflow, either.

Since you don't want to use fixed heights, but want the list element to render a scrollbar when necessary, you've got to be a bit sneaky. Still, it's a win-win. The browser gets what it wants. You get what you want.

Add this to your code:

#content {
  display: flex;
  flex-direction: column;
}

.big-list {
  height: 1px;
  flex-grow: 1;
}

So what you're doing is turning the list element's parent (#content) into a flex container for the sole purpose of enabling flex-grow on the child (.big-list).

You set a tiny fixed height on the list element (height: 1px). This satisfies the condition needed for an overflow. The browser gets what it wants.

Then, for the list element to fill remaining height, you add flex-grow: 1. You get what you want.

Since height is equivalent to flex-basis in a column-direction flex container, you can simplify to:

flex: 1 0 1px /* flex-grow, flex-shrink, flex-basis */
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
  • Thanks! You said "neither that element or any of its ancestors have a height limitation". But `.outer-grid` has `height:100vh`. Isn't that a height limitation? – Rob N Feb 07 '19 at 03:18
  • Yes. You're right. But that won't trigger the overflow on `.big-list`. For overflow purposes, it applies only to `.outer-grid`. I revised my answer with a correction. – Michael Benjamin Feb 07 '19 at 03:24
  • The MDN docs mention that `height` or `max-height` on an element allow `overflow` to have an effect. How did you know that `flex-basis` also does this? Is that mentioned somewhere? – Rob N Feb 07 '19 at 03:49
  • Yes. It's in the flexbox spec. `flex-basis` is the equivalent of `height` / `width` in a flex container. – Michael Benjamin Feb 07 '19 at 03:58
  • Thank you very much, you saved my night! ;) – Ambrus Tóth Apr 13 '21 at 16:29
-1

You can apply height to .big-list and .left-nav to height: calc(100vh - 51px); overflow-y: auto; of subtract height of header and search

.big-list {
    overflow-y: scroll;
    height:calc(100vh - 56px);
}

body {
    margin: 0;
}
.outer-grid {
    display: grid;
    height: 100vh;
    grid-template-columns: 180px 1fr;
    grid-template-rows: min-content 1fr;
}
.top-bar {
    grid-column-start: 1;
    grid-column-end: 3;
    border-bottom: 1px solid rgb(200, 200,200);
}
.header {
    font-weight: bold;
    margin: 8px;
}
.left-nav {
    overflow-y: auto;
    padding: 8px;
    border-right: 1px solid rgb(200, 200, 200);
    height: calc(100vh - 51px);
}
#content {
    padding-left: 8px;
    padding-right: 8px;
}
#search {
    width:100%;
}
.big-list {
    overflow-y: scroll;
    height:calc(100vh - 56px);
}
.big-list div {
    padding: 8px 0;
    border-top: 1px solid rgb(230,230,230);
}
<body>
  <div class="outer-grid">

    <div class="top-bar">
      <div class="header">Header</div>
    </div>

    <div class="left-nav">
      <div>Nav 1</div>
      <div>Nav 2</div>
      <div>Nav 3</div>        
    </div>

    <div id="content">
      <input id='search' type="text"/>

      <div class='big-list'>
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Row ... </div>    
 <div> Row ... </div>
 <div> Row ... </div>
 <div> Last Row </div>   
      </div>
    </div>
  </div>
</body>
Hiren Vaghasiya
  • 5,454
  • 1
  • 11
  • 25