1

I'm creating a list view with a fixed header and would like the list view's body to extend to the bottom of the page and still allow overflow on the y-axis. In practice runs, I was able to get everything working except I didn't try the idea of extending it to the bottom of the page until I went to put it into a page. Prior to this I was using fixed heights and everything was working as expected. Once I put it into my page, the list view then extended well beyond the bounds of the page and instead of extending to the bottom of the page and having overflow within the body, it just caused the page to have overflow instead.

Now, I've done some research on the topic and I've implemented the flexbox solution a few times successfully elsewhere in my web styles. However, this one is proving to be a bit of a pain and I'm not sure that I fully grasp the concept behind this approach.

I managed to create a minimal example of the issue, and during that process I managed to figure out that the one line in my CSS that causes issues is flex-direction: column within the .container class. When I remove this line, the list view only extends to the bottom of the page and the overflow occurs within the list view. However, adding it back will cause the list view to extend the page instead.

var testTable = document.getElementById("test-table");
for (let i = 0; i < 150; i++)
  testTable.innerHTML += '<tr><td>DATA ' + (i + 1) + '</td><td>' + i + '</td></tr>';
html,
body {
  margin: 0;
  height: 100%;
  background-color: #fc3;
  background-image: linear-gradient(45deg, #fc3, #f33);
}

.container {
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  height: 100%;
}

.test-header {
  height: 150px;
  background-color: #fff3;
}

.test-body {
  display: flex;
  flex-direction: column;
  background-color: #33f5;
}

.test-body table {
  width: 100%;
  table-layout: fixed;
}

.test-body-header {
  background-color: #fff5;
}

.test-body-body {
  flex: 1;
  overflow-y: auto;
}
<div class="container">
  <div class="test-header">A</div>
  <div class="test-body">
    <div class="test-body-header">
      <table>
        <tr>
          <th>Data ID</th>
          <th>Value</th>
        </tr>
      </table>
    </div>
    <div class="test-body-body">
      <table id="test-table"></table>
    </div>
  </div>
</div>

I've added some JavaScript to reduce the amount of markup required to reproduce the issue.


Why does adding flex-direction: column to my .container class, cause this undesired effect? Also, since I've explicitly set the height of my .test-header class to 150px, why isn't that height being displayed? Are the two effects related?

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
Hazel へいぜる
  • 2,751
  • 1
  • 12
  • 44

2 Answers2

1

to allow a flex child to scroll , you need to set overflow:hidden on the parent, else overflow remains visible and the child won't mind height's parent.

your CSS revisited

var testTable = document.getElementById("test-table");
for (let i = 0; i < 150; i++)
  testTable.innerHTML += '<tr><td>DATA ' + (i + 1) + '</td><td>' + i + '</td></tr>';
html,
body {
  margin: 0;
  height: 100%;
  background-color: #fc3;
  background-image: linear-gradient(45deg, #fc3, #f33);
}

.container {
  display: flex;
  flex-direction: column;
  height: 100%;
  /* overflow rule unnecessary yet here */
}

.test-header {
  height: /*1*/50px;/* down to fifty for the small snippet it stands along*/
  background-color: #fff3;
}

.test-body {
  flex: 1;/* tel it to use all free space */
  overflow: hidden;/* only free space, no more */
  display: flex;
  flex-direction: column;
  background-color: #33f5;
}

.test-body table {
  width: 100%;
  table-layout: fixed;
}

.test-body-header {
  background-color: #fff5;
}

.test-body-body {
  flex: 1;
  overflow: auto;/* let me scroll */
}
<div class="container">
  <div class="test-header">A</div>
  <div class="test-body">
    <div class="test-body-header">
      <table>
        <tr>
          <th>Data ID</th>
          <th>Value</th>
        </tr>
      </table>
    </div>
    <div class="test-body-body">
      <table id="test-table"></table>
    </div>
  </div>
</div>
G-Cyrillus
  • 101,410
  • 14
  • 105
  • 129
  • I hope you don't mind, but I'd like to give the check mark for this one to @Michael_B's answer. This definitely solved my problem, but I believe Michaels answer is more helpful to future readers. +1 from me! – Hazel へいぜる Sep 25 '19 at 17:57
1

Just make two minor adjustments and you're done.

1. Header problem

Since I've explicitly set the height of my .test-header class to 150px, why isn't that height being displayed?

Your header (.test-header) doesn't respect the 150px because the default setting for flex-shrink is 1 (enabled). This means that the flex item can shrink below its initial size in order to prevent an overflow of the container.

Because its sibling (.test-body) is consuming all remaining height in the container, and then more, the header is shrinking as much as it can.

You need to disable flex-shrink.

Instead of this:

.test-header {
   height: 150px;
 }

Try this:

.test-header {
   height: 150px;
   flex-shrink: 0;
 }

Or, even better, and as recommended by the spec, this:

.test-header {
   flex: 0 0 150px; /* fg, fs, fb */
 }

7.2. Components of Flexibility

Authors are encouraged to control flexibility using the flex shorthand rather than with its longhand properties directly, as the shorthand correctly resets any unspecified components to accommodate common uses.


2. Scrolling problem

Why does adding flex-direction: column to my .container class, cause this undesired effect?

A flex item, by default, cannot be smaller than its content along the main axis, which is vertical in flex-direction: column (full explanation).

This means that your content element (.test-body) is set to min-height: auto, which prevents an overflow condition, as the element simply expands to accommodate more content, and there's never a cause for a vertical scrollbar.

You need to override this setting. Add this to you code:

.test-body {
  min-height: 0; /* new */
  display: flex;
  flex-direction: column;
}

var testTable = document.getElementById("test-table");
for (let i = 0; i < 150; i++)
  testTable.innerHTML += '<tr><td>DATA ' + (i + 1) + '</td><td>' + i + '</td></tr>';
.container {
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  height: 100vh;
}

.test-header {
  flex: 0 0 150px; /* adjustment */
  background-color: #fff3;
}

.test-body {
  min-height: 0; /* new */
  display: flex;
  flex-direction: column;
  background-color: #33f5;
}

.test-body table {
  width: 100%;
  table-layout: fixed;
}

.test-body-header {
  background-color: #fff5;
}

.test-body-body {
  flex: 1;
  overflow-y: auto;
}

body {
  margin: 0;
  background-color: #fc3;
  background-image: linear-gradient(45deg, #fc3, #f33);
}
<div class="container">
  <div class="test-header">A</div>
  <div class="test-body">
    <div class="test-body-header">
      <table>
        <tr>
          <th>Data ID</th>
          <th>Value</th>
        </tr>
      </table>
    </div>
    <div class="test-body-body">
      <table id="test-table"></table>
    </div>
  </div>
</div>
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701