Prevent content from being hidden underneath the Virtual Keyboard by means of the VirtualKeyboard API

One of the problems on mobile devices is that the keyboard can hide some of your content. The VirtualKeyboard API aims to solve this.

~

The Problem

The current behavior on mobile devices is that as the keyboard gets shown, the Layout Viewport remains the same size but the Visual Viewport shrinks. Because layout items with position: fixed; bottom: 0; get anchored to the Layout Viewport, these items can become hidden underneath the Virtual Keyboard (or bleed out from the top in case they use top: 0;). Some devices counter this and offset the Layout Viewport so that the focussed content remains in view.


Layout Viewport and Visual Viewport Behavior when the Virtual Keyboard gets shown (src).

Once once you start scrolling the page upwards again, the whole Layout Viewport will move position, and thus the item-at-the-bottom will be hidden underneath the keyboard again.

~

A Theoretical Solution

To deal with this you could use the Visual Viewport API and manually offset items once the Virtual Keyboard gets shown.

let pendingUpdate = false;

function viewportHandler(event) {
	if (pendingUpdate) return;
	pendingUpdate = true;

	requestAnimationFrame(() => {
		pendingUpdate = false;
		
		// Stick to top
		document.querySelector('[data-stickto="top"]').style.transform = `translateY(${ Math.max(0, window.visualViewport.offsetTop)}px)`;
		
		// Stick to bottom
		if (window.visualViewport.offsetTop >= 0) {
			document.querySelector('[data-stickto="bottom"]').style.transform = `translateY(-${Math.max(0, window.innerHeight - window.visualViewport.height - window.visualViewport.offsetTop)}px)`;
		}
	});
}

window.visualViewport.addEventListener("scroll", viewportHandler);
window.visualViewport.addEventListener("resize", viewportHandler);

Having played with it, I found this approach to be impractical and lacking:

  • Extra Math needed to deal with overscrolling
  • iOS: Only updates after scroll has finished
  • Android (Emulator): Scrolling becomes glitchy

☝️ Feel free to point out if I got my demo wrong there. Test it on your mobile device using this URL: https://codepen.io/bramus/debug/ExXxOLQ

~

A Proper Solution

As mentioned in The Large, Small, and Dynamic Viewports there’s a new kid in town that can help us out here: the VirtualKeyboard API.

With the API you can programmatically — e.g. via JavaScript — trigger the Virtual Keyboard and get its dimensions, but what I find more interesting is that it also provides some CSS environment variables:

  • keyboard-inset-top
  • keyboard-inset-right
  • keyboard-inset-bottom
  • keyboard-inset-left
  • keyboard-inset-width
  • keyboard-inset-height

The keyboard insets are six environment variables that define a rectangle by its top, right, bottom, and left insets from the edge of the viewport. Default value of the keyboard insets are “0px”

By default all these variables have a value of 0px. When the keyboard gets shown the value for keyboard-inset-height will change to reflect the height of the Virtual Keyboard. You can then use this as the bottom-margin on an element that’s anchored to the bottom of the Viewport:

.bottom-box {
  position: fixed;
  bottom: 0;
  margin-bottom: calc(20px + env(keyboard-inset-height));
}


In the new behavior the Visual Viewport will not shrink as the Virtual Keyboard gets shown, and you — as a developer — can take the keyboard dimensions into account to position items.

🤔 The word “Viewport” in that image indeed is confusing there, as it’s not specified which Viewport it is about. To make things clear: they’re talking but the Visual Viewport. I’ve filed an issue to clarify on this.

In a layout that uses CSS Grid, you can easily take this value into account to create a bottom-zone where nothing may be placed:

body {
    display: grid;
    height: 100vh;
    grid-template:
      "messages"  1fr
      "input"     auto
      "keyboard"  env(keyboard-inset-height, 0px);
}

The Grid Area named keyboard with either be 0px high or take up the size of the Virtual Keyboard, thus pushing the input Grid Area upwards as the keyboard gets shown.

~

The catch

To enable this new behavior, you need this little JS snippet:

if ("virtualKeyboard" in navigator) {
  navigator.virtualKeyboard.overlaysContent = true;
}

Personally I find this JS-requirement a shortcoming of this API. An extension to the meta name="viewport" (or perhaps a new CSS at-rule or property on <html>?) to control the behavior seems like something handier.

Something like this:

<!-- Proposal -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, virtual-keyboard=overlays-content">

Or this:

/* Proposal */
html {
  virtual-keyboard: overlays-content;
}

(Names to be bikeshed but you get the point)

💭 I’ve filed an issue in the spec repo regarding this.

~

Browser Support

The VirtualKeyboard API is shipping with Chromium 94. Other browser vendors have not signalled any interest in it yet. Relevant Issues:

~

More Info

There’s a good post up on web.dev that goes into more detail on the JavaScript interface I quickly mentioned. Be sure to give a read if you want to use the VirtualKeyboard API with your JavaScript-based apps. It also taps into the virtualkeyboardpolicy attribute you can use along with that.

~

🔥 Like what you see? Want to stay in the loop? Here's how:

About the author

Bramus is a Freelance Web Developer from Belgium. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since (more …)

Join the Conversation

1 Comment

  1. I think it was a bad idea to not resize the layout viewport when the keyboard pops up.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.