# VirtualKeyboard API

> navigator.virtualKeyboard.overlaysContent stops the on-screen keyboard from resizing the viewport. boundingRect, geometrychange, show(), hide(), env() insets.

`navigator.virtualKeyboard` lets a page take over the layout work the browser normally does when the on-screen keyboard appears. Setting `overlaysContent = true` stops the browser from shrinking the viewport, the `geometrychange` event and `boundingRect` report where the keyboard is, six `keyboard-inset-*` CSS environment variables expose the same rectangle to stylesheets, and `show()` and `hide()` request the keyboard for editable elements marked `virtualkeyboardpolicy="manual"`.

The interface shipped in Chrome 94 and Edge 94 and is mirrored by Opera, Samsung Internet, Android WebView, and the Meta Quest Browser. Firefox ([Bugzilla 1730568](https://bugzil.la/1730568)) and Safari ([WebKit bug 230225](https://webkit.org/b/230225)) have not implemented it (BCD `api.VirtualKeyboard`). On a device without a system on-screen keyboard the object exists but `boundingRect` stays at zero and `geometrychange` never fires.

## Syntax

```js
navigator.virtualKeyboard.overlaysContent = true
navigator.virtualKeyboard.boundingRect
navigator.virtualKeyboard.show()
navigator.virtualKeyboard.hide()
navigator.virtualKeyboard.addEventListener('geometrychange', handler)
```

```css
.composer {
  padding-bottom: env(keyboard-inset-height, 0px);
}
```

`show()` and `hide()` return `undefined` and act asynchronously: the browser asks the system to show or hide the keyboard, waits, updates `boundingRect`, and then fires `geometrychange`. `boundingRect` is a `DOMRect` in CSS pixels relative to the layout viewport; the six environment variables `keyboard-inset-top`, `-right`, `-bottom`, `-left`, `-width`, and `-height` are updated from the same rectangle and read `0px` before the keyboard has ever appeared. `VirtualKeyboard` is exposed on `Window` only.

## Members

The interface has two attributes, two methods, and one event. `show()` and `hide()` take no arguments; their preconditions are listed with them because a call that misses one is dropped without an exception.

| Member | Type | Description |
|---|---|---|
| `overlaysContent` | `boolean`, default `false` | When `true`, the browser must not resize the document's viewport or visual viewport for the keyboard; the keyboard draws over the page and the page repositions itself from `boundingRect`. Honoured only in the top-level browsing context. |
| `boundingRect` | `DOMRect` (read-only) | Current keyboard rectangle; all zeros while the keyboard is hidden. |
| `show()` | `undefined` | Requests the keyboard. Ignored unless the window has sticky activation, the focused element is a form control or editing host, that element carries `virtualkeyboardpolicy="manual"`, and its `inputmode` is not `none`. |
| `hide()` | `undefined` | Dismisses the keyboard under the same activation and `virtualkeyboardpolicy="manual"` conditions. |
| `geometrychange` event | `Event` (`event.target.boundingRect`) | Fired after the keyboard is shown, hidden, or resized and `boundingRect` has been updated. |

The `virtualkeyboardpolicy` content attribute on editable elements takes `auto` (default: focusing the element shows the keyboard) or `manual` (the page calls `show()` itself); `inputmode="none"` suppresses the keyboard regardless of the policy.

## Exceptions

None.

Neither the setter nor the methods throw. A `show()` or `hide()` whose precondition fails simply returns, and an `overlaysContent` assignment inside an iframe is accepted by the setter but has no effect; Chromium reports both situations as console warnings rather than exceptions.

:::observed
Chrome logs `Setting overlaysContent is only supported from the top level browsing context` in the DevTools Console (Warning level) when `navigator.virtualKeyboard.overlaysContent = true` runs inside an iframe, and `Calling show is only supported if user has interacted with the page` when `show()` runs before any user interaction, for example from `DOMContentLoaded`. Both strings are emitted by Chromium's [`virtual_keyboard.cc`](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/virtualkeyboard/virtual_keyboard.cc) (chromium.googlesource.com).
:::

## Examples

Both examples test for `'virtualKeyboard' in navigator`. Where it is missing they keep the browser's default behaviour (viewport resize) and use `window.visualViewport`, which Firefox 91 and Safari 13 implement, to learn how much space the keyboard took.

### Pinning a message composer above the keyboard

A chat composer should sit directly above the keyboard, not scroll away with the page. With the API the page opts out of viewport resizing and pads the composer by the keyboard height; without it the composer follows the visual viewport's bottom edge.

```js
const composer = document.querySelector('.composer');

if ('virtualKeyboard' in navigator) {
  navigator.virtualKeyboard.overlaysContent = true;
  navigator.virtualKeyboard.addEventListener('geometrychange', (event) => {
    const { height } = event.target.boundingRect;
    composer.style.setProperty('--keyboard-height', `${height}px`);
  });
} else if (window.visualViewport) {
  const vv = window.visualViewport;
  vv.addEventListener('resize', () => {
    const covered = window.innerHeight - vv.height - vv.offsetTop;
    composer.style.setProperty('--keyboard-height', `${Math.max(0, covered)}px`);
  });
}
```

```css
.composer {
  position: fixed;
  bottom: 0;
  padding-bottom: var(--keyboard-height, env(keyboard-inset-height, 0px));
}
```

The CSS fallback chain means a browser that supports the environment variable but has not yet fired `geometrychange` still gets `0px`, and a browser with neither the API nor the variable gets the plain fixed composer.

### Showing the keyboard for a canvas-based editor

A spreadsheet drawn on a `<canvas>` has no `<input>` to focus, so it hosts a hidden `contenteditable` element with `virtualkeyboardpolicy="manual"` and calls `show()` when a cell is tapped. Browsers without the API fall back to focusing the element, which shows the keyboard through the default `auto` policy.

```html
<div id="cell-input" contenteditable="true" virtualkeyboardpolicy="manual" inputmode="text"></div>
```

```js
const input = document.querySelector('#cell-input');
const canvas = document.querySelector('canvas');

canvas.addEventListener('pointerup', () => {
  input.focus();
  if ('virtualKeyboard' in navigator) {
    navigator.virtualKeyboard.show(); // the tap supplied the sticky activation
  }
});

document.querySelector('#done').addEventListener('click', () => {
  if ('virtualKeyboard' in navigator) {
    navigator.virtualKeyboard.hide();
  } else {
    input.blur(); // default policy: losing focus dismisses the keyboard
  }
});
```

Because `show()` requires the policy attribute, removing `virtualkeyboardpolicy="manual"` from the markup turns the call into a no-op without an error; keep the attribute and the call together.

## See also

- [Media Session API](/reference/capabilities/media-session/), another Chromium-first integration with system UI
- [Window Management API](/reference/capabilities/window-management/), for geometry questions about screens rather than keyboards
- [Core Web Vitals for PWAs](/reference/performance/core-web-vitals/), where keyboard-driven viewport resizes show up as layout shift
- [VirtualKeyboard API: the VirtualKeyboard interface](https://w3c.github.io/virtual-keyboard/#the-virtualkeyboard-interface) (w3c.github.io)
- [Full control with the VirtualKeyboard API](https://developer.chrome.com/docs/web-platform/virtual-keyboard) (developer.chrome.com)
- [Bugzilla 1730568: Implement the VirtualKeyboard API](https://bugzil.la/1730568) (bugzilla.mozilla.org)
- [WebKit bug 230225: Implement the VirtualKeyboard API](https://webkit.org/b/230225) (webkit.org)