DEV Community

Chandra Sekhar Dey
Chandra Sekhar Dey

Posted on

Building a Browser-Based Keyboard Tester: Ghosting, NKRO, and the Quirks of the KeyboardEvent API

I built KeyboardTest.tech — a free, no-install keyboard diagnostic tool that runs entirely in the browser. Here's what I learned wrestling with the KeyboardEvent API to detect stuck keys, ghosting, and rollover limits client-side, with zero server logging.
The core problem: the browser doesn't tell you what you think it does

keydown and keyup seem simple until you try to build something like an NKRO (N-Key Rollover) checker. A few gotchas that cost me real debugging time:

event.code vs event.key — key gives you the character produced (locale + modifier-dependent), code gives you the physical key position (layout-independent). For a hardware tester, you almost always want code. Using key will silently break for non-QWERTY layouts.
The OS eats some combos before JS ever sees them. Certain OS-level shortcuts (screenshot combos, some Meta/Cmd chords) never reach the page. You can't "test" what the browser never receives — worth being upfront about this as a limitation rather than pretending full hardware-level access.
Ghosting isn't a JS bug, it's a hardware/matrix limitation. Cheap keyboards use a key matrix that can only resolve certain simultaneous key combinations. When you hold enough keys at once, the matrix can't disambiguate and either drops a keypress or reports a "phantom" one. From the browser, this shows up as a keydown that never fires (not a keyup that fires incorrectly) — which means your ghosting detector is really just: track expected keys pressed vs. actual keydown events received, then diff.
Detecting rollover without native access

There's no navigator.keyboard.getRollover() — so an NKRO tester really just:

Listens for every keydown and adds event.code to a Set.
Listens for keyup and removes it.
Tracks the maximum size the Set ever reached during a session.

That max size is your empirical rollover ceiling. It's not perfect (you're bounded by however many keys the user's fingers can physically hold down during testing), but for practical "is this a 6KRO or NKRO keyboard" purposes, it works well — most users can get 8-10+ keys down at once if they try.

Chatter/double-typing detection

Switch chatter (one physical press registering as two) shows up as two keydown events for the same code within an implausibly short window with no keyup in between. A naive Date.now() timestamp diff on consecutive keydowns for the same code, with a threshold tuned against real bouncy-switch recordings, catches the overwhelming majority of cases without false-positiving on fast intentional double-taps (those have a keyup between them).

Zero-logging by design

Everything above runs entirely client-side — no keystroke data is ever sent to a server. For a tool whose entire premise is "let me mash random keys to test my hardware," that's a privacy requirement, not just a nice-to-have. If you're building anything similar, it's worth architecting for this from the start rather than bolting on privacy later — it also simplifies GDPR/CCPA compliance considerably since there's no keystroke data to protect in the first place.

Stack notes

Built with Next.js (App Router) + TypeScript + Tailwind. Static generation for the tool pages themselves (no server round-trip needed since all the diagnostic logic is client-side JS), with MDX for the blog/guide content.

Top comments (0)