Is it possible to set height value so that it is both inheritable and acts as min-height?

48 Views Asked by At

I have a single-column layout, where the body element acts as a column and has a distinctive background-color. I want the body element to fill the viewport height (currently using 100vh for this) so that inner elements can use height: 100%, but if the content of body is larger than the viewport, the body should grow (so that its background-color continues with the content).

Here's some demo code:

// This is just a helper code for the demo
// it's unrelated to the problem. Please, review the CSS block
let height = '';
document.querySelector('button').addEventListener('click', () => {
  const newHeight = height === '' ? '2400px' : '';
  height = newHeight;
  const filler = document.querySelector('.content-filler')
  filler.style.height = newHeight;
})
* {
  box-sizing: border-box;
}

html {
  background-color: white;
}

body {
  width: 300px;
  background-color: #b39ddb;
  height: 100vh; /* fixed height is defined here */
  padding: 0;
  margin: 0;
  margin-inline: auto;
}

.column {
  height: 100%; /* this is inherited from the body's 100vh */
  display: flex;
  flex-direction: column;
}

.bottom-row {
  margin-top: auto;
  padding: 12px;
  background: blueviolet;
}

.content-filler {
  flex-shrink: 0;
  background: #673ab7;
  color: white;
  padding: 8px;
}
<div class="column">
  <div style="padding: 8px">
    Hello world
    <div>
      <button>Toggle large content</button>
    </div>
    <br>
    <div class="content-filler">Dynamic content</div>
  </div>

  <div class="bottom-row">bottom-row</div>
</div>

The problem with this implementation is that when you have content of large height, the body element stays fixed at 100vh and its background color appears to be "cut off".

Obviously this is expected for the fixed height value, but how can I solve this problem?

If I change height: 100vh to min-height: 100vh, then the child element's height: 100% would no longer take effect.

I tried using something like height: minmax(100vh, fit-content) for the height value, but unfortunately it didn't work.

Is there a way to solve this other then converting all elements in the tree to flex columns, starting from body?


UPDATE: One important use-case that I missed: further children of .column need to also be able to set height: 100%, and so on further down the tree.

1

There are 1 best solutions below

8
timetowonder On

I can't believe it, I think I solved it! Though feels more like winning a lottery of accidentally finding what works.

Use these styles for body:

body {
  display: grid;
  min-height: 100vh;
}

I fiddled a lot with setting body to flex, but it required updating all children to flex columns as well. But to my surprise, display: grid behaves differently! In essence, it allows children of an element whose height is set by min-height to grow using height: 100%! And this is exactly what I was trying to achieve.

Here's the updated demo:

// This is just a helper code for the demo
// it's unrelated to the problem. Please, review the CSS block
let height = '';
document.querySelector('button').addEventListener('click', () => {
  const newHeight = height === '' ? '2400px' : '';
  height = newHeight;
  const filler = document.querySelector('.content-filler')
  filler.style.height = newHeight;
})
* {
  box-sizing: border-box;
}

html {
  background-color: white;
}

body {
  width: 300px;
  background-color: #b39ddb;
  display: grid; /* <-- this is the solution */
  min-height: 100vh; /* <-- this is the solution */
  padding: 0;
  margin: 0;
  margin-inline: auto;
}

.column {
  height: 100%; /* this is inherited from the body's 100vh */
  display: flex;
  flex-direction: column;
}

.bottom-row {
  margin-top: auto;
  padding: 12px;
  background: blueviolet;
}

.content-filler {
  flex-shrink: 0;
  background: #673ab7;
  color: white;
  padding: 8px;
}
<div class="column">
  <div style="padding: 8px">
    Hello world
    <div>
      <button>Toggle large content</button>
    </div>
    <br>
    <div class="content-filler">Dynamic content</div>
  </div>

  <div class="bottom-row">bottom-row</div>
</div>

So far I found only one issue: when my page is inside of an iframe, it has unwanted overscroll. Fortunately in my case I can control this and set .is-iframe class to body, for which I change the display value back to block.

I will play around some more and if I don't find serious issues I'll mark this as the answer