# Keyboard Lock and Pointer Lock

> Capture selected physical keys and relative pointer movement for an immersive interface, with activation checks, unlock paths, and feature fallbacks.

Keyboard Lock lets a page capture physical-key presses that the operating system makes available, while Pointer Lock hides the cursor and delivers relative pointer movement to one element. Both are intended for focused interfaces such as games and remote-access applications.

MDN marks both `Keyboard.lock()` and the promise-returning form of `requestPointerLock()` as limited availability. Keyboard Lock is also restricted to secure contexts, so an interface needs independent checks and a usable unlocked path.

## Where it is supported

MDN marks both APIs as limited availability. Keyboard Lock requires a secure context and JavaScript-initiated fullscreen; Pointer Lock support and the promise-returning request form vary across browsers.

## Syntax

```js
await navigator.keyboard.lock(["KeyW", "KeyA", "KeyS", "KeyD"]);
navigator.keyboard.unlock();

await element.requestPointerLock();
document.exitPointerLock();
```

Calling `keyboard.lock()` without a list requests all valid key codes that the operating system allows. A later call replaces the earlier set. The Keyboard Lock specification dispatches locked keys while the document is fullscreen. Pointer Lock keeps `clientX`, `clientY`, `screenX`, and `screenY` constant while `movementX` and `movementY` continue to report relative motion.

## Parameters

`keyboard.lock(keyCodes)` accepts an optional sequence of `KeyboardEvent.code` values. `element.requestPointerLock(options)` accepts an optional `unadjustedMovement` boolean; when it is `true`, the Pointer Lock specification requests movement without operating-system mouse acceleration.

## Exceptions

Keyboard Lock's `lock()` algorithm requires the active top-level browsing context; it does not check transient user activation. Chrome's documentation requires JavaScript-initiated fullscreen before Keyboard Lock. The promise rejects with `InvalidStateError` outside the active top-level browsing context, `InvalidAccessError` for an invalid code, and `AbortError` when a later lock request supersedes a pending one.

Pointer Lock requires transient activation unless the document previously released a successful lock through `exitPointerLock()`. A sandboxed iframe needs the `allow-pointer-lock` token, and a request for unsupported unadjusted movement rejects with `NotSupportedError` under the Pointer Lock algorithm.

:::caution
The Keyboard Lock specification gives `Press ESC to exit fullscreen` and `Press and hold ESC to exit fullscreen` as examples of messages a user agent might show. It says a user agent may need to change its UX when Escape is locked; it does not require either exact prompt ([Keyboard Lock](https://wicg.github.io/keyboard-lock/), WICG).
:::

## Examples

The example reports locked controls after Pointer Lock is confirmed and the Keyboard Lock promise fulfills. On-screen controls remain available when either request fails.

### Locking controls with an unlocked fallback

The first click enters fullscreen. A second click requests Pointer Lock. After `pointerlockchange` confirms that lock, the page requests Keyboard Lock. If an API is absent, the interface keeps ordinary keyboard and pointer handling and reports that focused controls are unavailable.

```html
<section id="control-surface" tabindex="0" aria-label="Interactive control surface">
  <p id="lock-status" role="status">Use the buttons or click this area to start.</p>
  <output id="position">x: 0, y: 0</output>
  <div id="screen-controls" aria-label="On-screen controls">
    <button type="button" data-dx="0" data-dy="-1">Up</button>
    <button type="button" data-dx="-1" data-dy="0">Left</button>
    <button type="button" data-dx="1" data-dy="0">Right</button>
    <button type="button" data-dx="0" data-dy="1">Down</button>
  </div>
</section>

<script type="module">
const surface = document.querySelector("#control-surface");
const status = document.querySelector("#lock-status");
const position = document.querySelector("#position");
const controls = document.querySelector("#screen-controls");
let x = 0;
let y = 0;

function move(dx, dy) {
  x += dx;
  y += dy;
  position.value = `x: ${x}, y: ${y}`;
}

controls.addEventListener("click", (event) => {
  const button = event.target.closest("button[data-dx]");
  if (!button) return;
  move(Number(button.dataset.dx), Number(button.dataset.dy));
});

surface.addEventListener("click", async (event) => {
  if (event.target.closest("#screen-controls")) return;
  const canLockKeyboard = "keyboard" in navigator &&
    "lock" in navigator.keyboard;
  const canLockPointer = "requestPointerLock" in surface;
  const canFullscreen = "requestFullscreen" in surface;

  if (!canLockKeyboard || !canLockPointer || !canFullscreen) {
    status.textContent = "Use the on-screen controls.";
    return;
  }

  if (document.fullscreenElement !== surface) {
    try {
      await surface.requestFullscreen();
      status.textContent = "Click the surface again to lock controls.";
    } catch (error) {
      status.textContent = `Fullscreen was not entered: ${error.name}`;
    }
    return;
  }

  const request = surface.requestPointerLock();
  request?.catch((error) => {
    status.textContent = `Pointer Lock failed: ${error.name}`;
  });
});

document.addEventListener("pointerlockchange", async () => {
  if (document.pointerLockElement !== surface) {
    navigator.keyboard?.unlock?.();
    status.textContent = "Controls are unlocked.";
    return;
  }

  try {
    await navigator.keyboard.lock(["KeyW", "KeyA", "KeyS", "KeyD"]);
    status.textContent = "Controls locked. Press Escape to leave.";
  } catch (error) {
    document.exitPointerLock();
    status.textContent = `Keyboard Lock failed: ${error.name}`;
  }
});

document.addEventListener("pointerlockerror", () => {
  status.textContent = "Pointer Lock failed; use the on-screen controls.";
});
document.addEventListener("mousemove", (event) => {
  if (document.pointerLockElement !== surface) return;
  move(event.movementX, event.movementY);
});
</script>
```

The event handler waits for confirmed Pointer Lock before reporting success. It releases Pointer Lock if Keyboard Lock fails, releases Keyboard Lock when Pointer Lock ends, and keeps the defined on-screen controls usable when support is absent or a request fails. Relative pointer movement and button presses update the same position output.

### Keeping the on-screen fallback available

The example never hides `#screen-controls`. Its button handler calls the same `move()` function as relative pointer movement, so the fallback remains usable before a lock starts and after a request fails.

## See also

- [User activation](/reference/capabilities/user-activation/), for the activation state used by Pointer Lock requests
- [Screen Capture API](/reference/capabilities/screen-capture/), for sharing a display in remote-access interfaces
- [Keyboard Lock](https://wicg.github.io/keyboard-lock/) (wicg.github.io)
- [Pointer Lock 2.0](https://w3c.github.io/pointerlock/) (w3c.github.io)