DEV Community

Cover image for Building a Browser-Based Keyboard Tester with Vanilla JavaScript
Zihang Dong 董子航
Zihang Dong 董子航

Posted on • Originally published at toolknit.com

Building a Browser-Based Keyboard Tester with Vanilla JavaScript

Have you ever bought a new mechanical keyboard only to discover a dead switch two weeks later — past the return window? Or spilled coffee on your laptop and wondered which keys survived?

I built a free keyboard tester that runs entirely in the browser. No downloads, no backend, no data collection. Just open the page, press keys, and see which ones work.

In this post, I'll walk through the key technical decisions and JavaScript patterns I used to build it.

The Core Idea

Display a virtual keyboard on screen. When the user presses a physical key, the corresponding virtual key lights up. Simple concept, but there are a few tricky parts:

  1. Mapping physical keys to virtual keys — Not all keyboards have the same layout
  2. Capturing ALL key presses — Including Tab, F-keys, and other keys browsers like to hijack
  3. Counting keystrokes — For detecting chattering (double-firing keys)
  4. N-Key Rollover testing — Tracking multiple simultaneous key holds

Key-to-Element Mapping

Each virtual key in the HTML has a data-key attribute matching the KeyboardEvent.code value:

<div class="key" data-key="KeyA">A</div>
<div class="key" data-key="Space">Space</div>
<div class="key" data-key="ShiftLeft">Shift</div>
Enter fullscreen mode Exit fullscreen mode

I chose event.code over event.key because code represents the physical key position, not the character it produces. This means the tester works regardless of keyboard language or layout (QWERTY, AZERTY, Dvorak, etc).

function getKeyElement(code) {
    return document.querySelector('.key[data-key="' + code + '"]');
}
Enter fullscreen mode Exit fullscreen mode

Capturing Every Key Press

Browsers intercept certain keys by default: Tab switches focus, F5 refreshes, F11 toggles fullscreen. To capture these, I use preventDefault() on both keydown and keyup events, with useCapture: true:

document.addEventListener('keydown', function(e) {
    e.preventDefault();
    e.stopPropagation();

    const code = e.code;
    const keyEl = getKeyElement(code);

    if (keyEl) {
        keyEl.classList.add('active');     // Visual press feedback

        if (!testedKeys.has(code)) {
            testedKeys.add(code);
            keyEl.classList.add('tested'); // Permanent "tested" state
            updateStats();
        }
    }

    return false;
}, true); // useCapture = true
Enter fullscreen mode Exit fullscreen mode

And the corresponding keyup handler to remove the "active" visual state:

document.addEventListener('keyup', function(e) {
    e.preventDefault();
    e.stopPropagation();

    const keyEl = getKeyElement(e.code);
    if (keyEl) keyEl.classList.remove('active');
    return false;
}, true);
Enter fullscreen mode Exit fullscreen mode

Preventing Browser Shortcuts

Some keys need extra prevention at the window level:

window.addEventListener('keydown', function(e) {
    if (e.key === 'Tab' || e.key === 'F1' || e.key === 'F5' || 
        e.key === 'F11' || e.key === 'F12') {
        e.preventDefault();
    }
});
Enter fullscreen mode Exit fullscreen mode

This catches Tab (focus switching), F1 (help), F5 (reload), and F11/F12 (fullscreen/devtools) before the browser processes them.

The Keystroke Counter

This was a recent addition and probably the most useful feature for diagnosing keyboard problems. Mechanical keyboards can develop "chattering" — where one press registers as two or more inputs. This is caused by worn switch contacts bouncing.

The counter tracks every key press individually:

const keyCounts = {};
let totalKeystrokes = 0;

// Inside keydown handler:
keyCounts[code] = (keyCounts[code] || 0) + 1;
totalKeystrokes++;
updateKeystrokeUI(code, label);
Enter fullscreen mode Exit fullscreen mode

The UI dynamically creates a card for each key, showing its press count. If a key registers 3+ presses, it's highlighted in amber as a potential chattering issue:

function updateKeystrokeUI(code, label) {
    let entry = document.getElementById('kc-' + code);
    if (!entry) {
        entry = document.createElement('div');
        entry.id = 'kc-' + code;
        entry.className = 'flex items-center justify-between ...';
        entries.appendChild(entry);
    }

    const count = keyCounts[code];
    const isHigh = count >= 3;
    entry.innerHTML = '<span class="' + (isHigh ? 'text-amber-400' : 'text-slate-300') + '">' 
        + label + '</span><span>' + count + (isHigh ? ' !' : '') + '</span>';
}
Enter fullscreen mode Exit fullscreen mode

The reset function clears all counts without affecting the main keyboard test state:

window.resetKeystrokeCounter = function() {
    for (var k in keyCounts) delete keyCounts[k];
    totalKeystrokes = 0;
    document.getElementById('keystroke-entries').innerHTML = '';
};
Enter fullscreen mode Exit fullscreen mode

Progress Tracking

Users need to know how many keys they've tested out of the total. The stats panel shows tested/total/percentage and color-codes the progress:

function updateStats() {
    const tested = testedKeys.size;
    const pct = Math.round((tested / totalKeysCount) * 100);

    document.getElementById('keys-pressed').textContent = tested;
    document.getElementById('keys-percent').textContent = pct + '%';

    // Color feedback
    const pctEl = document.getElementById('keys-percent');
    if (pct >= 100) {
        pctEl.className = '... text-emerald-400'; // Green = done
    } else if (pct >= 50) {
        pctEl.className = '... text-yellow-400';  // Yellow = halfway
    }
}
Enter fullscreen mode Exit fullscreen mode

Virtual Key Clicks

Not everyone has a physical keyboard handy (maybe they're testing remotely). Virtual keys are clickable too:

allKeys.forEach(function(keyEl) {
    keyEl.addEventListener('click', function() {
        const code = this.getAttribute('data-key');
        this.classList.add('active');

        if (!testedKeys.has(code)) {
            testedKeys.add(code);
            this.classList.add('tested');
            updateStats();
        }

        // Keystroke counter for clicks too
        keyCounts[code] = (keyCounts[code] || 0) + 1;
        totalKeystrokes++;

        setTimeout(() => this.classList.remove('active'), 200);
    });
});
Enter fullscreen mode Exit fullscreen mode

Lessons Learned

  1. event.code > event.key — Always use code for physical key identification. key changes with language/layout.

  2. useCapture: true is essential — Without it, some keydown events get swallowed before your handler runs.

  3. A Set is perfect for tracking tested keys — O(1) lookups, no duplicates, and .size gives you the count for free.

  4. Chattering detection needs a counter, not a boolean — Just knowing a key "was pressed" isn't enough. You need to know how many times it registered per intended press.

  5. No framework needed — The entire tool is vanilla HTML + CSS + JS. It loads instantly and works offline. Sometimes the simplest stack is the best stack.

Try It

The keyboard tester is live at toolknit.com/tools/keyboard-tester.html. It's part of ToolKnit, a collection of 30+ free browser-based tools for files, images, video, and more.

If you have a keyboard collecting dust, plug it in and test it. You might be surprised what you find.


Have you built similar browser-based utilities? I'd love to hear about the edge cases you ran into. Drop a comment below!

Top comments (0)