Forget 100vh

Milo Xeon, 2020

So, you need your content block to take up the whole page, but become taller to accommodate more content if needed.

You type “height: 100%”.

It does nothing.

The problem is the height value is what is inherited, not the actual computed height.

So you start googling.

Wrong solutions

height: 100% to everything

Something like this:

html, body {
    height: 100%;
}

.root {
    height: 100%;
}

/* many more lines of similar code */

It’s wrong because your content will crop if you set overflow to hidden. Also, it completely breaks scroll events on window because there is nothing to scroll.

display: table-cell

html {
    width: 100%;
    height: 100%;
    display: table;
}

body {
    width: 100%;
    display: table-cell;
}

It’s wrong because it only works with one descendant of body. Its children can never be at least tall as itself.

height: 100vh

.content {
    height: 100vh;
}

It’s right, but it works bad on mobile devices. There is -webkit-fill-available hack that yields seemingly unpredictable values on different platforms.

The right solution

<html>
  <body>
    <div class="fill-start">
      <!--  any amount of children  -->
      <div>
        <div>
          <div>
            <div class="fill-end">
              <!--  content goes here  -->
            </div>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>

This should work no matter the amount of children.

html {
  min-height: 100%;
  display: flex;
  flex-direction: column;
}

body,
.fill-start,
.fill-start * {
  display: flex;
  flex-direction: column;
  flex-grow: 2;
}

.fill-end {
  display: initial;
}

.fill-end * {
  display: initial;
  flex-grow: initial;
}

Nested divs resemble arbitrary amount of children. Star selector is here to keep things nice and declarative.

The only possible way to ensure that the content block is at least as tall as our viewport, no matter the amount of parents, is — you guessed it — to make styling go down the chain and style all its parents. The other approach is just to make the content block at least as tall as 100vh or -webkit-fill-available, but that renders inconsistently across devices, especially the mobile ones.

There always can be multiple fill-end elements if you don’t need to style something:

<div class="fill-start">
  <div class="fill-end">
    <!-- sidebar -->
  </div>
  <div class="necessary-element">
    <div class="fill-end">
      <!-- content -->
    </div>
  </div>
</div>

We use flex-grow to expand the children. We use .fill-start and not just body * because we don’t want scripts and other invisible tags to appear.

.fill-start should be a child of body. So we set body, .fill-start and all its children to become flex items and to grow inside their parents, which are also flex items based on the same exact selector. This all come together like Lego pieces!

When it’s time to end this nested expansion, we reset the display property of .fill-end, making its children obey the normal document flow. We also undo the spell of the original selector by setting display property to what it used to be instead of flex.

It works just as needed — no matter the amount of nesting, our content container will be at least as tall as our viewport, but it will also become taller to accommodate the content. And you only need to define the start and the end of container expansion.

Here’s the demo with overflow:

Thank you!