Filling the remaining height of a container while handling overflow in CSS (IE8+, Firefox, Chrome, Safari)

The issue

I want to have a container with two elements inside of it, stacked on top of each other.  I don't know how tall the top element is, so I want it to take up as much vertical space as the content it holds.  I don't know how tall the bottom element is, so I want it to take up whatever space remains, and if it has more content, I want it to have a vertical scrollbar.

 

In other words, I want something like this:

 

 I searched everywhere online and could not find a cross-browser, CSS-only solution.

 

The solution

To solve this problem, I had to use display: table.  The problem with tables is that it's hard to limit the height of them: if their content is too tall, the table will grow in height.  I found two suggestions to overcome this online.  The first suggestion was to give the container for the table overflow: hidden or overflow: auto.  Any content that overflows will either be hidden or reachable via a scrollbar.  The problem with this approach is that the header is inside of the scrollable area, which is not desired.  The other suggestion is give the table display: block, which allows you to limit its height.  The problem with that solution is that then we no longer inherit the wonderful properties of tables that we need.

The solution I ended up coming up with started with the table approach.  The very basic table approach is to make the container have display: table and to give each of the elements in the table display: table-row.  At that point, the issue comes when the content of the bottom element is too tall for the container.  If the content is taller than the container, the table's height will grow.  We don't want the table's height to grow.  Setting height and max-height don't work.  To limit the height of the bottom row in the table, I had to take the content out of the regular flow.  If there is no content in the bottom row, then the bottom row will take up the correct amount of space.  The easiest way to take content out of the flow of a page is to wrap it in another element and give that element position: absolute.  The problem with position: absolute is that now our content isn't restricted to the boundaries of the bottom row.  To fix this, we have to scope position: absolute.  We do that by making the table row have position: relative.  Unfortunately, making the bottom row position: relative doesn't work because position: relative doesn't work on table rows.  To overcome this, I added yet another wrapper around the absolutely-positioned content.  I put position: relative on this new wrapper and gave it width: 100% and height: 100%.  Now, the absolutely-positioned element is positioned with respect to the dimensions of the bottom row.  Thus, we can give it top: 0, bottom: 0, left: 0, and right: 0 and it will fill the space of the bottom row.  If we want the content to show a scrollbar, we just add overflow: auto.  And that does the trick!  Unfortunately, it only works in IE11+.

If you don't need to support below IE11, then here's the full code:

HTML

<div class="table container">
    <div class="table-row header">
        <div>This is the header whose height is unknown</div>
        <div>This is the header whose height is unknown</div>
        <div>This is the header whose height is unknown</div>
    </div>
    <div class="table-row body">
        <div class="body-content-wrapper">
            <div class="body-content">
                <div>This is the scrollable content whose height is unknown</div>
                <div>This is the scrollable content whose height is unknown</div>
                <div>This is the scrollable content whose height is unknown</div>
                <div>This is the scrollable content whose height is unknown</div>
                <div>This is the scrollable content whose height is unknown</div>
                <div>This is the scrollable content whose height is unknown</div>
                <div>This is the scrollable content whose height is unknown</div>
                <div>This is the scrollable content whose height is unknown</div>
                <div>This is the scrollable content whose height is unknown</div>
                <div>This is the scrollable content whose height is unknown</div>
                <div>This is the scrollable content whose height is unknown</div>
                <div>This is the scrollable content whose height is unknown</div>
                <div>This is the scrollable content whose height is unknown</div>
                <div>This is the scrollable content whose height is unknown</div>
                <div>This is the scrollable content whose height is unknown</div>
                <div>This is the scrollable content whose height is unknown</div>
                <div>This is the scrollable content whose height is unknown</div>
                <div>This is the scrollable content whose height is unknown</div>
            </div>
        </div>
    </div>
</div>

CSS

.table {
    display: table;
}

.table-row {
    display: table-row;
}

.container {
    width: 400px;
    height: 300px;
}

.header {
    background: cyan;
}

.body {
    background: yellow;
    height: 100%;
}

.body-content-wrapper {
    width: 100%;
    height: 100%;
    position: relative;
}

.body-content {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    overflow: auto;
}

And a working example on  JSFiddle

I wasn't satisfied with the IE support for this solution.  Thus, I decided to dive further.  So the issue with IE10 and below is that the body-content-wrapper needs to have display: table-cell.  As soon as I add display: table-cell, the bottom content no longer renders.  The reason for this is that position: relative is undefined for table-cell elements (see: https://stackoverflow.com/a/17527089/55732).  I was able to get around this by adding yet another wrapper around the content for the table-cell.  And that seems to work in IE8+!

Here's the full code:

HTML

<div class="table container">
    <div class="table-row header">
        <div>This is the header whose height is unknown</div>
        <div>This is the header whose height is unknown</div>
        <div>This is the header whose height is unknown</div>
    </div>
    <div class="table-row body">
        <div class="table-cell body-content-outer-wrapper">
            <div class="body-content-inner-wrapper">
                <div class="body-content">
                    <div>This is the scrollable content whose height is unknown</div>
                    <div>This is the scrollable content whose height is unknown</div>
                    <div>This is the scrollable content whose height is unknown</div>
                    <div>This is the scrollable content whose height is unknown</div>
                    <div>This is the scrollable content whose height is unknown</div>
                    <div>This is the scrollable content whose height is unknown</div>
                    <div>This is the scrollable content whose height is unknown</div>
                    <div>This is the scrollable content whose height is unknown</div>
                    <div>This is the scrollable content whose height is unknown</div>
                    <div>This is the scrollable content whose height is unknown</div>
                    <div>This is the scrollable content whose height is unknown</div>
                    <div>This is the scrollable content whose height is unknown</div>
                    <div>This is the scrollable content whose height is unknown</div>
                    <div>This is the scrollable content whose height is unknown</div>
                    <div>This is the scrollable content whose height is unknown</div>
                    <div>This is the scrollable content whose height is unknown</div>
                    <div>This is the scrollable content whose height is unknown</div>
                    <div>This is the scrollable content whose height is unknown</div>
                </div>
            </div>
        </div>
    </div>
</div>

CSS

.table {
    display: table;
}

.table-row {
    display: table-row;
}

.table-cell {
    display: table-cell;
}

.container {
    width: 400px;
    height: 300px;
}

.header {
    background: cyan;
}

.body {
    background: yellow;
    height: 100%;
}

.body-content-outer-wrapper {
    height: 100%;
}

.body-content-inner-wrapper {
    height: 100%;
    position: relative;
    overflow: auto;
}

.body-content {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
}

And a working example on JSFiddle .