Suppose you're making a sticky footer or centering some content relative to the viewport. You want to stretch the body
element to the full height of the browser viewport while also letting it grow even further to match its content. This task was surely solved a bazillion times, and it should be as easy as pie. Right? Right?
The state-of-the-art way
Sure! Applying min-height: 100vh
to the body
element should do the trick. Here, 100vh
means that the initial body
height will take 100% of the viewport height, whereas the use of min-height
instead of height
will let the body
element grow even more if necessary. Isn't it exactly what we need?
Well... Almost. If we open such a page in a typical mobile browser (such as iOS Safari or Android Chrome), it will be scrollable regardless of the size of its content. Even if the page has no content at all, its bottom will still disappear beneath the bottom UI panel of the browser!
The reason for this is fairly simple. UI elements in these browsers shrink after the scroll, providing additional space for the actual content. A height of 100vh
corresponds to the maximum possible viewport height. Since the initial viewport height is smaller, the body
element with a min-height
of 100vh
initially exceeds the viewport height regardless of its content.
The known fix for this issue looks like this:
html {
height: -webkit-fill-available; /* We have to fix html height */
}
body {
min-height: 100vh;
min-height: -webkit-fill-available;
}
This solution has a minor glitch in Chrome: when the browser height increases, the body
height stays the same, getting out of sync with the viewport height. Aside from that, this approach solves the issue.
However, we now have to fix the html
height. If that's the case, shouldn't we use an older, more robust solution?
The old-school way
Since we couldn't avoid fixing the html
height, let's try the good old way that involves passing a height of 100% from the html
element.
Let's apply min-height: 100%
to the body
element, where 100% is the full height of its parent (namely, html
). A percentage height on a child requires its parent to have a fixed height, so we have to apply height: 100%
to the html
element, thereby fixing its height to the full viewport height.
Since the percentage height of the html
element in mobile browsers is calculated relative to the minimal viewport height, the above-mentioned scroll issue doesn't bug us anymore!
html {
height: 100%; /* We still have to fix html height */
}
body {
min-height: 100%;
}
This solution is not as pretty as the 100vh
one, but it's been used since time immemorial, and it will work, that's for sure!
Well... Not quite. Apparently, the gradient applied to such a body
element will be cut at the html
height (in other words, at the viewport height, or, to be more precise, at the minimal viewport height).
It happens because of the fixed html
height, and it doesn't matter whether it's height: 100%
or height: -webkit-fill-available
.
Of course, this can be "fixed" by applying the gradient to the body
content, but that's just not right. The page background should be applied to the body
element, and the html
element should stretch to its content. Can we achieve that?
The missing way
I suggest another way of stretching the body
element to the full viewport height without the above-mentioned issues. The core idea is to use flexbox, which enables a child element to stretch even to a parent with non-fixed dimensions while retaining the ability to grow further.
First, we apply min-height: 100%
to the html
element to stretch it to the full minimal viewport height. Then we use display: flex
and flex-direction: column
to turn it into a flex-container with a vertical main axis. Finally, we apply flex-grow: 1
to the body
element, thereby stretching it to the html
height.
The align-self
property of the body
element implicitly has the stretch
value, so the body
width already matches the html
width.
html {
min-height: 100%; /* Look, it's not fixed anymore! */
display: flex;
flex-direction: column;
}
body {
flex-grow: 1;
}
Now both html
and body
can stretch to their content, and, since we're using the percentage height, there are no issues with mobile browsers whatsoever. Neat!
Notes
It should be obvious that the flexbox-based solution works for any depth. It can easily be used in cases where the content is being rendered to an element inside the
body
, and not thebody
element itself. It's a typical scenario with React or Vue, for example.As you might've noticed, the direction of the main axis of the flex-container shouldn't matter. I just think that the vertical axis is more elegant in this case, and I didn't really test the other variant. I don't see how it can possibly break, but who knows.
The flexbox-based solution doesn't work in IE. Not at all. But you don't support it anyway, do you?
Top comments (7)
Hi, Thanks for this article ! Very helpful for me =)
I just have a quick question. In the last solution, why did you apply a flex-grow: 1 to the body ? I thought that flex-grow were used only when we have more than 1 child.
You're welcome!
We need
flex-grow: 1
even thoughbody
is the only (visible) child ofhtml
, because without itbody
won't stretch tohtml
(because the default isflex-grow: 0
, which means "don't stretch this flex item across the main axis").Thank you, Leonid! This is just what I was needing!
But you don't support it anyway, do you?
Definitely not, lol. Thanks for the idea.
Thank you, that was really helpful.
Why would you ever apply flex properties to html and body? I do not believe this is correct.
hmm why not ?