Skip to content

API in Capabilities

Keyboard Lock and Pointer Lock

Published

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.

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.

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.

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.

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.

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

Section titled “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.

<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.

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.

Specifications

SpecificationStatus
Keyboard LockWICG draft
Pointer Lock 2.0W3C draft