· deepdives · 5 min read
Understanding the Idle Detection API: A Game Changer for User Engagement
Learn how the Idle Detection API lets web apps detect when users are idle or their screens are locked, how to implement it (with safe fallbacks), and how to use it responsibly to improve engagement and resource usage.

Why the Idle Detection API matters
Modern web apps strive to be responsive to users’ context. Knowing whether a user is actively interacting with the device - or has stepped away - unlocks a range of features: adaptive notifications, intelligent presence indicators for chat/collaboration, saving battery by pausing heavy work, and better retention strategies.
The Idle Detection API provides a standardized, privacy-proxied way for web pages to learn two things:
- the user’s interaction state (“active” vs “idle”)
- the screen state (“locked” vs “unlocked”)
Because the API is permission-gated and focused on local signals rather than remote tracking, it fills a gap between crude heuristics (timers watching input events) and no signal at all.
For a specification overview, see the WICG repo and developer docs: https://github.com/WICG/idle-detection and https://developer.mozilla.org/en-US/docs/Web/API/Idle_Detection_API
How it works at a glance
- The page (or worker) requests permission via the API.
- When granted, the browser monitors (locally) user input and screen lock state.
- Your script listens to changes and reacts. The browser surfaces a simple API: userState and screenState.
Important: The browser intentionally keeps the signal local and permissioned to reduce fingerprinting and privacy risks.
Basic usage (feature-detect + permission)
The API is still experimental in many browsers. Always feature-detect and provide a fallback (see the fallback section below).
// Feature-detect & request permission
if ('IdleDetector' in window) {
try {
const permission = await IdleDetector.requestPermission();
if (permission !== 'granted') {
console.warn('Idle Detection permission not granted');
} else {
const idleDetector = new IdleDetector();
idleDetector.addEventListener('change', () => {
const { userState, screenState } = idleDetector; // 'active' | 'idle' and 'locked' | 'unlocked'
console.log(`User is ${userState}, screen is ${screenState}`);
// React to change (update UI, notify server, pause background updates, etc.)
handleIdleChange({ userState, screenState });
});
await idleDetector.start();
console.log('Idle Detector started');
}
} catch (err) {
console.error('Idle Detection start failed:', err);
}
} else {
// Fallback strategy (see below)
enableFallbackIdleDetection();
}
Notes:
- IdleDetector.requestPermission() is required and returns a string like
'granted'
or'denied'
. - IdleDetector exposes properties
userState
andscreenState
and fires'change'
when either changes.
Refer to MDN for up-to-date API nuances: https://developer.mozilla.org/en-US/docs/Web/API/Idle_Detection_API
Integrating presence with a server (safe pattern)
Common use: update a presence indicator on a server (e.g., for chat). Use short-lived signals and avoid using raw idle events for security-sensitive logic.
Example (WebSocket):
// Assume ws is an open WebSocket
function handleIdleChange({ userState }) {
const payload = {
type: 'presence:update',
idle: userState === 'idle',
timestamp: Date.now(),
};
ws.send(JSON.stringify(payload));
}
Best practice:
- Send presence updates sparsely (e.g., on change, or at a throttled interval) to reduce noise.
- Don’t treat ‘idle’ as an authentication or authorization signal.
- Allow users to opt-out of sharing presence with other users.
Fallback: when Idle Detection isn’t available
Because browser support is limited and permission may be denied, always provide a fallback. Use these signals in combination:
- Page Visibility API (document.visibilityState) - detects tab hidden/visible.
- Input event watchers (mousemove, keydown, touchstart) with a timer.
Simple fallback example:
function enableFallbackIdleDetection(timeout = 60_000) {
let lastActivity = Date.now();
const reset = () => {
lastActivity = Date.now();
};
['mousemove', 'mousedown', 'keydown', 'touchstart', 'scroll'].forEach(e =>
window.addEventListener(e, reset, { passive: true })
);
document.addEventListener('visibilitychange', () => {
// treat hidden as "idle-ish" or special state
console.log('Visibility changed:', document.visibilityState);
});
setInterval(() => {
const isIdle = Date.now() - lastActivity > timeout;
// notify app logic when state flips (implement your own debounce/edge detection)
handleFallbackIdleState(isIdle);
}, 1000);
}
Limitations:
- You cannot detect screen lock state with this fallback.
- This approach is more fingerprintable if misused - be transparent and minimize signal retention.
Real-world use cases
- Presence indicators for chat/collaboration: show “away” or “idle” reliably when a user walks away.
- Notification management: defer non-critical push/desktop notifications while a user is idle, then batch them.
- Resource optimization: pause expensive background animations, syncing, or polling when the user is away.
- UX improvements: offer contextual prompts (e.g., “Welcome back - continue where you left off”) when a user returns.
- Multiplayer gaming / match-making: mark players as idle so they aren’t matched into new sessions.
Use case caution: avoid using idle to infer sensitive user attributes (health, routines), or to serve manipulative content.
Privacy, security and design considerations
The API exists because naive approaches (tracking mouse/keyboard timeouts) created privacy and fingerprinting concerns. Follow these guidelines:
- Always request permission and explain why your app needs it.
- Limit retention: store presence state only as long as necessary.
- Be explicit in UI: let users opt out and see how their presence is used.
- Aggregate and anonymize telemetry when possible.
- Do not derive sensitive conclusions from idle data (e.g., assuming personal routines or medical conditions).
- Never use idle as the sole basis for security decisions (e.g., logging out without user confirmation may be acceptable, but not for auth checks).
Browser vendors may further restrict this API over time; plan for progressive enhancement.
Best practices for implementation
- Feature-detect and gracefully degrade.
- Throttle updates to the server (e.g., only on change). Avoid constant sends.
- Use explicit consent flows and in-product messaging.
- Provide users a simple toggle in settings to disable presence/idle sharing.
- Test across devices (desktop, mobile) because input and lock semantics differ.
- Be careful with cross-platform differences (mobile OSes may suspend background tasks or screen locking behaves differently).
Testing and debugging tips
- Simulate inactivity by not interacting with the page; lock the screen to test screenState if supported.
- Use the browser console to inspect whether IdleDetector exists and what states it surfaces.
- On platforms with limited support, rely on your fallback and unit tests to ensure correct behavior.
- Keep logs ephemeral-your server logs for debugging should purge presence logs regularly.
Browser support & resources
The Idle Detection API is experimental and has had limited availability due to privacy concerns. Always check current compatibility before shipping: see the MDN reference and the WICG repository for timeline and notes.
- MDN: https://developer.mozilla.org/en-US/docs/Web/API/Idle_Detection_API
- WICG: https://github.com/WICG/idle-detection
- Intro / examples: https://web.dev/idle-detection/
Conclusion
The Idle Detection API is a powerful tool for making web apps more context-aware and considerate of user attention. When used responsibly - with explicit permission, transparent UX, and privacy-preserving patterns - it can meaningfully improve engagement and resource efficiency. Because the API is experimental in many browsers, build with graceful fallbacks and treat idle signals as helpful heuristics, not absolute truths.
References
- Idle Detection API docs: https://developer.mozilla.org/en-US/docs/Web/API/Idle_Detection_API
- WICG idle-detection: https://github.com/WICG/idle-detection
- web.dev introduction: https://web.dev/idle-detection/