- Overlapping bottom navigation bar despite 100vh in iOS Safari
- Solutions
- 100%
- stretch / -webkit-fill-available
- JavaScript
- Conclusion
- About Dev Tips
- Webkit fill available css
- Using -webkit-fill-available
- Does this really work?
- Related posts
- Stretching body to full viewport height: the missing way
- The state-of-the-art way
- The old-school way
- The missing way
- Notes
Overlapping bottom navigation bar despite 100vh in iOS Safari
»100vh« may not behave as expected for some mobile browsers and the bottom of your content will be partially hidden behind the browser’s bottom bar (i.e. below the »fold«).
First of all, let’s have a look at the issue by checking out the following example. It’s a simple page with 2 absolutely positioned boxes in the top left corner ( .top ) and the right bottom corner ( .bottom ). These boxes are wrapped within an element ( .container ) with a width of 100vw and a height of 100vh . You may have something similar in your project, such as a fullscreen modal/lightbox with a header/footer.
> > This should span accross the full viewport, right? Well, in the left screenshot below, you can see that in iOS Safari the bottom navigation bar actually overlaps your content, i.e. your content is below the »fold«—although you may have expected that it’s not part of the viewport.
In the right screenshot, you can see how one would expect the layout to be. The container spans between the top address bar and the bottom navigation bar.
This is a well-known issue and unfortunately intentional, as it prevents other problems, as Benjamin Poulain explained in his reply to a WebKit bug ticket regarding this issue.
This is completely intentional. It took quite a bit of work on our part to achieve this effect.
The base problem is this: the visible area changes dynamically as you scroll. If we update the CSS viewport height accordingly, we need to update the layout during the scroll. Not only that looks like shit, but doing that at 60 FPS is practically impossible in most pages (60 FPS is the baseline framerate on iOS).
It is hard to show you the «looks like shit» part, but imagine as you scroll, the contents moves and what you want on screen is continuously shifting.
Dynamically updating the height was not working, we had a few choices: drop viewport units on iOS, match the document size like before iOS 8, use the small view size, use the large view size.
From the data we had, using the larger view size was the best compromise. Most website using viewport units were looking great most of the time.
So, it’s not a bug—and no fix is planned for this.
Solutions
Luckily, this doesn’t have to be the most depresssing answer ever. How do we go on from this? There’s a couple of solutions.
100%
Depending on your use case, it may be enough to simply use 100% instead of 100vh , especially for fixed/sticky elements (as 100% will be relative to the »real« viewport).
However, if your element is nested somewhere in the DOM, this may not work out (as 100% will be relative to the parent elements which are only as tall as the content they contain). And that may have been the motivation why you wanted to use 100vh in the first place.
stretch / -webkit-fill-available
Intrinsic and extrinsic sizing is a new CSS functionality that extends the sizing properties with keywords that represent content-based »intrinsic« sizes and context-based »extrinsic« sizes. This allows CSS to more easily describe boxes that fit their content or fit into a particular layout context.
One of these keywords is stretch which formerly was known as fill , fill-available , and its prefixed spin-offs -moz-available and -webkit-fill-available . We can make use of this functionality and because CSS skips over style declarations it doesn’t understand, we can implement fallbacks for all of these possible implementations.
(Hint: Autoprefixer will compile stretch to -webkit-fill-available and -moz-available automatically.)
JavaScript
JavaScript is always the »last stronghold« for stuff that’s not possible with pure CSS. Using CSS variables, you can pass the value of window.innerHeight into your CSS and update this variable every time the viewport is resized.
updateViewportHeight = ( < document.setProperty(window.innerHeight>px`); >; window.addEventListener(updateViewportHeight();
In your CSS, you can consume this variable as follows.
If you can’t use CSS variables in your project (e.g. due to browser support concerns), you can also update the height of your affected elements directly from within your script.
document.querySelector(updateViewportElements = ( < container.window.innerHeight>px`; >; window.addEventListener(updateViewportElements();
Conclusion
Unfortunately, there isn’t a one-size-fits-all solution for this issue. You should try the aforementioned solutions top down and be very conscientious with your cross-browser/cross-device testing.
About Dev Tips
This site was started by Thomas a few years ago and ever since, we published all kinds of tips, tricks, techniques, tutorials, and tidbits about what we know and love with the software developer community with the goal of helping others build, solve problems, and learn.
Our glossary attempts to explain terms and buzzwords often used in software development in a few simple words with the purpose of having explanations than can be easily referenced.
All articles are licensed under CC-BY-SA 4.0. Code snippets are licensed under MIT, unless otherwise noted. Thanks a ton to all authors for their awesome contributions.
You can find a list of all articles in our archive. If you’d like to contribute to this site, simply head over to its GitHub repository, fork it, make your contributions, and send a pull request.
Webkit fill available css
Not long ago there was some buzz around how WebKit handles 100vh in CSS, essentially ignoring the bottom edge of the browser viewport. Some have suggested not using 100vh , others have come up with different alternatives to work around the problem. In fact, this issue goes further back a few years when Nicolas Hoizey filed a bug with WebKit on the subject (the short of it: WebKit says this is “intentional” 🧐 ).
The other day I was doing some work with a basic flexbox layout – header, main, sticky footer – the kind we’ve all seen and used many times before:
HEADER GOES HERE MAIN GOES HERE
I began running some browser tests on my iPhone, and that’s when I noticed that my sticky footer wasn’t looking so sticky:
The footer was hiding below Safari’s menu bar. This is the 100vh bug (feature?) that Nicolas originally uncovered and reported. I did a little sleuthing – hoping that maybe by now a non-hacky fix had been found – and that’s when I stumbled upon my own solution (btw, it’s totally hacky):
🔥 TIL a #CSS trick to handle that annoying mobile viewport bug with `100vh` in WebKit (iOS Safari)! #WebDev #ProTip pic.twitter.com/lefD0Klqzd
— Matt Smith (@AllThingsSmitty) April 25, 2020
Using -webkit-fill-available
The idea behind -webkit-fill-available – at least at one point – was to allow for an element to intrinsically fit into a particular layout, i.e., fill the available space for that property. At the moment intrinsic values like this aren’t fully supported by the CSSWG.
However, the above problem is specifically in WebKit, which does support -webkit-fill-available . So with that in mind, I added it to my ruleset with 100vh as the fallback for all other browsers.
This code was updated to include the html selector after I was told that Chrome is updating the behavior to match Firefox’s implementation.
And now the sticky footer is right where I want it to be in mobile Safari!
Does this really work?
The jury seems to be out on this. I’ve had no problems with any of the tests I’ve run and I’m using this method in production right now. But I did receive a number of responses to my tweet pointing to other possible problems with using this (e.g., the effect on rotating devices, Chrome not completely ignoring the property, etc.).
Will -webkit-fill-available work in every scenario? Probably not, cuz let’s be honest: this is the web, and it can be damn hard to build. But, if you’re having a problem with 100vh in WebKit and you’re looking for a CSS alternative, you might want to try this.
Related posts
Stretching body to full viewport height: the missing way
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 the body 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?