Idle Detection Badge — Free Heuristic + Native IdleDetector Demo

Idle Detection Badge · Dashboards · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Working-by-default heuristic
Needs zero permissions or special browser support.
Real activity events tracked
mousemove, keydown, pointerdown, touchstart, and scroll.
Native IdleDetector opt-in
Progressive enhancement behind an explicit button click.
Locked-screen detection
screenState surfaces a state no heuristic alone can reach.
Clean handoff between modes
The heuristic timer stops the moment the native API takes over.
Named permission outcomes
States plainly whether the request was denied or unsupported.
Live last-activity readout
A running "Xs ago" counter alongside the badge.
Visible active mode label
Always shows whether heuristic or native mode is driving the badge.

About this UI Snippet

Idle Detection Badge — A Heuristic That Works Everywhere, Enhanced by the Real API

Screenshot of the Idle Detection Badge snippet rendered live

Most "idle detection" demos reach straight for the IdleDetector interface and quietly break everywhere it isn't available — which, in practice, is almost everywhere: it's Chromium-only, requires an explicit permission grant through IdleDetector.requestPermission(), and is routinely disabled or blocked inside sandboxed preview iframes like the one likely rendering this demo. This snippet inverts that priority. The primary, always-on mode is a genuine activity heuristic built from real DOM events; the native API is wired in as an optional, opt-in upgrade.

The heuristic that actually runs

Every mousemove, keydown, pointerdown, touchstart, and scroll event updates lastActivityAt to the current timestamp. A 500ms interval, heuristicTick(), compares that timestamp against IDLE_THRESHOLD_MS (six seconds here) and flips the badge to "Idle" the moment that much real time has passed with zero activity — then flips straight back to "Active" the instant any tracked event fires again. Nothing here depends on permissions, browser support, or an installed context; it's the same technique behind countless "away" statuses in chat apps, just made honest about being a heuristic rather than dressed up as something more authoritative.

The real API, opted into deliberately

Clicking "Try real Idle Detection API" calls IdleDetector.requestPermission() behind a capability check for 'IdleDetector' in window. If granted, a live IdleDetector instance is started with a 60-second threshold (the API's practical minimum) and its change event reports both userState ('active'/'idle') and screenState ('locked'/'unlocked') — the latter is something no DOM-event heuristic could ever detect, since a locked screen fires no browser events at all. When it successfully takes over, the heuristic timer is cleared and the mode label switches from "heuristic mode" to "native mode."

Why this order, not the reverse

Attempting the real API first and falling back only on failure would mean most visitors briefly see a permission prompt attempt, or a silent failure, before anything useful renders. Starting from a fully working heuristic and treating the native API as a bonus — exactly the shape used by this library's canvas audio frequency bars snippet for microphone access — means the badge is meaningful from the first paint no matter what the browser or embedding context allows.

A third state a heuristic can't reach

The "Locked" badge state only ever appears via the real API's screenState, and the UI is explicit that this is a native-only capability — the heuristic mode simply has no equivalent. Pair this badge with a team presence list or collaborator presence bar for a multi-user version of the same pattern.

Customizing it

Tune IDLE_THRESHOLD_MS, add a "step away" auto-logout timer keyed off the idle state, or feed the state into a websocket to broadcast presence to other users in a real collaborative app.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain why the heuristic mode is built to run first and unconditionally, with the real IdleDetector API wired in only as an opt-in enhancement behind a button click, rather than the more obvious-seeming approach of trying IdleDetector first and falling back to the heuristic on failure. It's a good prompt for reasoning about the actual mechanics too — ask what screenState reports that no DOM-event heuristic could ever detect, and why detector.start() is called with a 60-second threshold rather than something shorter. For extensions, ask it to add a configurable idle threshold input, broadcast the idle/active/locked state over a WebSocket for a real multi-user presence feature, or add an auto-logout countdown that starts once the badge goes idle. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build an "Idle Detection Badge" in plain HTML, CSS, and JavaScript — no libraries.

Requirements:
- A badge element that shows one of three states — Active, Idle, or Locked — with distinct colors for each, plus a "last activity" readout and a label showing which detection mode is currently driving the badge (heuristic or native).
- CRITICAL: make the PRIMARY, always-working detection method a real heuristic, not the native browser API. Attach listeners for mousemove, keydown, pointerdown, touchstart, and scroll on the window, record the timestamp of the most recent event, and run a recurring check (e.g. every 500ms) that flips the badge to Idle once a configurable threshold (a handful of seconds) has passed with no activity, flipping straight back to Active the moment any tracked event fires again. This heuristic must work with zero permissions and in every browser, since it is the mode most viewers of this demo will actually experience.
- Add a button that, only when clicked, attempts to use the real native IdleDetector API as a progressive enhancement: check 'IdleDetector' in window first, then call IdleDetector.requestPermission() inside a try/catch, and only if permission is granted, construct a new IdleDetector, listen for its change event to read both userState ('active'/'idle') and screenState ('locked'/'unlocked'), and call detector.start(). On success, stop the heuristic's interval timer and switch the mode label to native, and use screenState to show the Locked badge state that the heuristic alone cannot detect.
- On any failure of the native path — the API not existing (the common case, since IdleDetector is Chromium-only), permission denied, or blocked by a permissions policy (a likely scenario inside a sandboxed preview iframe) — leave the heuristic running uninterrupted and show a specific status message explaining what happened, disabling the "try real API" button so it's clear that mode isn't available rather than leaving it clickable and silently failing again.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSAn "Active" badge renders immediately in heuristic mode.
  2. 2
    Stop moving the mouse or typingAfter the threshold, the badge flips to "Idle" automatically.
  3. 3
    Move the mouse or press a keyIt flips straight back to "Active."
  4. 4
    Click "Try real Idle Detection API"Requests permission for the native IdleDetector where available.
  5. 5
    Grant or deny the permissionThe status line reports exactly what happened either way.
  6. 6
    Watch the mode labelIt reads "heuristic mode" or "native mode" depending on what's active.

Real-world uses

Common Use Cases

Chat and collaboration apps
Pair with a team presence list.
Support agent dashboards
Show agent availability alongside a status dashboard.
Session timeout warnings
Trigger a warning modal after a real idle period.
Collaborative editors
Kiosk and shared-device apps
Detect a walked-away user to reset to a home screen.
Analytics engagement tracking
Measure genuine active time versus idle tab time.

Got questions?

Frequently Asked Questions

The native IdleDetector interface is implemented only in Chromium browsers, requires an explicit permission grant via IdleDetector.requestPermission(), and is frequently disabled by default or blocked entirely inside sandboxed contexts like the preview iframe likely rendering this demo. Rather than depend on that narrow path, the badge starts in a heuristic mode that tracks real mouse and keyboard events directly — no permission needed — and only switches to native mode if you explicitly request it and it succeeds.

It listens for mousemove, keydown, pointerdown, touchstart, and scroll events on the window and records the timestamp of the most recent one. A recurring check compares the current time against that timestamp, and if more time has passed than the configured threshold (six seconds in this demo, tunable via IDLE_THRESHOLD_MS) with zero activity, the badge flips to Idle. Any tracked event firing again flips it straight back to Active.

The heuristic can only ever infer idleness from events that fire in the browser tab, so it has no way to know if the screen is locked — a locked device fires no DOM events, but the tab itself may still technically be open. The real IdleDetector API's screenState property reports 'locked' directly from the OS, which is why this snippet's "Locked" badge state is only reachable through the native API, never the heuristic.

Requesting idle-detection permission triggers a real browser permission prompt, and attempting that automatically on page load would be an unwanted surprise for most visitors of a demo page. Making it an explicit opt-in click respects that it's a meaningful permission request, and means the badge is already fully functional in heuristic mode before you ever decide whether to grant it.

Move the activity event listeners and the setInterval heuristic check into a mount effect, storing lastActivityAt and the current state in refs or component state. Keep the IdleDetector setup in the button's click handler exactly as shown, and make sure to call detector's abort signal (or otherwise stop it) and clear the heuristic interval in your component's cleanup function so neither keeps running after unmount.