JavaScript Idle Detection Table
| Piece | What it does | Field note |
|---|---|---|
IdleDetector.requestPermission() | Explicit ask | Separate permission - 'allow idle detection' prompt |
IdleDetector.start({threshold}) | Begin watching | threshold: 60s minimum - granularity is user privacy |
detector.userState | active / idle | The USER is idle - distinct from the SCREEN being idle |
detector.screenState | locked / unlocked | Screen lock is a separate signal - pause on lock too |
onchange | The transition event | Both states fire one event - branch on both fields |
Chromium-only | The support gate | Firefox/Safari no - feature-detect and degrade to timers |
Privacy posture | Why so gated | Knowing human presence is surveillance-adjacent - hence permission |
Legit uses | Status apps | Chat availability, draft auto-save, kiosk session resets - NOT tracking |
Idle Detection answers 'is the human still here': requestPermission (a dedicated prompt), then start({threshold: 60_000}) - and the detector reports userState (active/idle) and screenState (locked/unlocked) with one onchange per transition.
Bottom line: the API is Chromium-only and deliberately gated - knowing human presence is surveillance-adjacent, hence the separate permission, the 60-second minimum threshold, and active-tab requirements. The honest uses: chat availability, kiosk session resets, and draft auto-save ('save before the user walks away and loses the tab').
The honest part: idle detection is NOT activity tracking - the states are coarse (active/idle at a minute granularity), the user granted a named permission, and the design intent is presence for the CURRENT app, not behavior history.
How to use
- Gate and start: const ctrl = new AbortController(); await IdleDetector.requestPermission(); await detector.start({ threshold: 60_000, signal: ctrl.signal }).
- Branch on both fields: detector.onchange = () => { if (detector.userState === 'idle') markAway(); if (detector.screenState === 'locked') pause(); } - user and screen idle independently.
- Auto-save on away: on idle, flush the draft to localStorage - the user who walked away mid-sentence comes back to everything.
Frequently asked questions
Why does Idle Detection have its own permission and a 60-second floor?
Because presence is sensitive. Knowing a human is at the machine (versus merely a tab being open) enables status surveillance, engagement pressure and behavior profiling - so the API demands a DEDICATED permission prompt (not bundled with any other grant), enforces a minimum threshold of 60 seconds (no fine-grained 'they stopped moving 3 seconds ago'), and only reports while the page is visible and focused. Each gate answers a specific abuse: the prompt is informed consent, the floor is coarse granularity, the visibility rule scopes it to the app the user is actually using. An idle API without these would be a keylogger's dream.
What is the difference between userState and screenState?
Two independent signals. userState (active/idle) is about the HUMAN: keyboard, mouse, direct interaction with the window. screenState (locked/unlocked) is about the MACHINE: the session may be unlocked while the human is idle (reading a book next to the PC), or locked while they just stepped out. Features branch differently: chat availability keys on userState (reading, not typing, still means present), while auto-save and security-sensitive pauses key on screenState (a locked session means the user cannot interact at all). The onchange fires for either transition - the handler must branch on both fields rather than assuming which changed.
Why is auto-saving drafts the killer use for idle detection?
Because it fixes the worst data-loss story at the exact right moment. Users walk away mid-sentence, the tab gets closed by accident or memory pressure, and hours of writing vanish. Idle detection says precisely 'the user stopped interacting a minute ago' - the moment to flush the draft to localStorage or the server WITHOUT interrupting an active typewriter (saving mid-typing is fine, but idle is the unambiguous 'they are not here to see a flicker'). The pattern: on idle, save; on active again, notify 'we saved your draft while you were away'. A presence signal used for preservation, not surveillance - the API's best-natured application.
How should a feature handle the Chromium-only reality?
Feature-detect and degrade to the old tools. if (!('IdleDetector' in window)) - fall back to the classic heuristics: debounced input/mousemove listeners for in-page idleness (they cannot see 'user left entirely', but they cover in-page idle), or plain timers. The graceful degradation for chat availability is a manual status toggle; for auto-save, the fallback is periodic or on-input saving (which arguably should exist ANYWAY as the base layer - idle detection is the optimization that saves at the perfect moment, not the only save). Never gate the core function on the API: idle detection refines the timing, the persistence exists independently.