Skip to content

Capabilities · API

Idle Detection API: watching for user and screen inactivity

Published Updated

In one line: The Idle Detection API’s IdleDetector interface lets a page ask whether the user has interacted with the screen or device within a given threshold, and whether the screen is locked, firing a change event whenever either state flips.

Per MDN, starting an IdleDetector requires the idle-detection permission, and IdleDetector.requestPermission() requires transient user activation — it must be called from within a user gesture, such as a click handler:

startButton.addEventListener('click', async () => {
const permission = await IdleDetector.requestPermission();
if (permission !== 'granted') {
console.error('Idle detection permission denied.');
return;
}
await startIdleDetector();
});

start() takes a threshold in milliseconds and an optional AbortSignal. Per the WICG specification, start() rejects with a TypeError when threshold is below 60,000ms (60 seconds) — there is no way to ask for finer-grained reporting:

async function startIdleDetector() {
const controller = new AbortController();
const idleDetector = new IdleDetector();
idleDetector.addEventListener('change', () => {
console.log(`User: ${idleDetector.userState}, Screen: ${idleDetector.screenState}`);
});
await idleDetector.start({
threshold: 60_000,
signal: controller.signal,
});
}

userState reports "active" or "idle"; screenState reports "locked" or "unlocked". Per MDN, both return null before start() is called, and start() gives them their initial values.

Per MDN’s compatibility data, IdleDetector ships only in Chromium-based browsers, from Chrome version 94, and requires a secure context. Firefox and Safari do not implement it.

async function watchIdleState(onChange) {
if (!('IdleDetector' in window)) {
// Unsupported: there is no OS-level idle/lock state to report here, so
// just report unknown instead of guessing.
onChange({ userState: 'unknown', screenState: 'unknown' });
return;
}
const permission = await IdleDetector.requestPermission();
if (permission !== 'granted') {
onChange({ userState: 'unknown', screenState: 'unknown' });
return;
}
const idleDetector = new IdleDetector();
idleDetector.addEventListener('change', () => {
onChange({ userState: idleDetector.userState, screenState: idleDetector.screenState });
});
await idleDetector.start({ threshold: 60_000 });
}
  • Feature-detect with 'IdleDetector' in window before referencing the class — it does not exist in Firefox or Safari.
  • Call IdleDetector.requestPermission() from inside a user gesture; calling it without transient user activation fails per spec.
  • Do not pass a threshold below 60,000ms — start() rejects with a TypeError instead of just reporting more slowly.
  • Treat userState and screenState as null before start() is called; do not read them synchronously right after construction.
  • Provide a working fallback experience for Firefox and Safari, which do not currently implement this API, rather than degrading silently.

Specifications

SpecificationStatus
None.