DEV Community

loading...
Cover image for At least as tall as viewport, but taller if needed (the proper solution)

At least as tall as viewport, but taller if needed (the proper solution)

mvoloskov profile image Miloslav Voloskov ・2 min read

TL;DR: Codepen link

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 */
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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.

Author

Discussion (4)

pic
Editor guide
Collapse
jamesthomson profile image
James Thomson

Interesting approach. Can you explain why you need 3 additional nested divs between fill-start and fill-end? I'd also be wary of the * selectors. Yes fill-end resets fill-start, however that doesn't mean it doesn't go down the chain and apply the styling to ALL child elements. Seems an unnecessary overhead when you could just be more declarative.

Collapse
mvoloskov profile image
Miloslav Voloskov Author • Edited

Nested divs resembles arbitrary amount of children. Star selector is the peak declarativeness.

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 — make styling go down the chain and style all its parents. The other approach is just make the content block at least as tall as 100vh or -webkit-fill-available, but that works bad on mobile devices.

As soon as viewport-related units work this bad, I see my approach as the only viable option aside maybe from doing the same with tables + table cells or grids. I like flexbox more. If you have a more elegant solution, I'm more than happy to see it!

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>
Enter fullscreen mode Exit fullscreen mode
Collapse
jamesthomson profile image
James Thomson • Edited

Star selector is the peak declarativeness.

Sorry, I meant explicit.

Nested divs resembles arbitrary amount of children.

Gotcha. Will have to play around with your approach with more use cases, but it's an interesting solution to the whole 100vh mess that we find ourselves in due to mobile browsers. Thanks for the article.

Collapse
dmahely profile image
Doaa Mahely

Great post Miloslav, I like the way you explain all the options.
I had this same problem with one of my projects for a couple of months now and haven't been able to solve it. I gotta try your solution!