'},{c:'Slash',l:'/',sub:'?'},
{c:'ShiftRight',l:'⇧ Shift',w:2.75}
],
[
{c:'ControlLeft',l:'Ctrl',w:1.4},
{c:'MetaLeft',l:'⌘ Meta',w:1.3},
{c:'AltLeft',l:'Alt',w:1.3},
{c:'Space',l:'Space',w:6.4},
{c:'AltRight',l:'Alt',w:1.3},
{c:'MetaRight',l:'⌘',w:1.3},
{c:'ContextMenu',l:'☰',w:1.3},
{c:'ControlRight',l:'Ctrl',w:1.4}
]
],
// navigation / arrows cluster (rendered separately on wide screens)
navRows: [
[{c:'Insert',l:'Ins'},{c:'Home',l:'Home'},{c:'PageUp',l:'PgUp'}],
[{c:'Delete',l:'Del'},{c:'End',l:'End'},{c:'PageDown',l:'PgDn'}],
[],
[{c:'',l:'',spacer:true},{c:'ArrowUp',l:'↑'},{c:'',l:'',spacer:true}],
[{c:'ArrowLeft',l:'←'},{c:'ArrowDown',l:'↓'},{c:'ArrowRight',l:'→'}]
],
// ---- runtime state ----
held: {}, // code -> true while physically down
tested: {}, // code -> true once ever pressed
chatter: {}, // code -> count of suspicious re-triggers (key chatter)
_lastUpAt: {}, // code -> timestamp of last keyup (for chatter detection)
last: null, // { key, code, keyCode, which, location, repeat }
maxRollover: 0, // most simultaneous keys ever registered
presses: 0, // total keydown (non-repeat) events
listening: false,
capsOn: false,
numpadEnabled: false, // toggle to show numpad in the testable set
everCelebrated: false,
// listeners
_down: null, _up: null, _blur: null,
init(){
this._down = (e) => this.onDown(e);
this._up = (e) => this.onUp(e);
this._blur = () => this.clearHeld();
window.addEventListener('keydown', this._down);
window.addEventListener('keyup', this._up);
window.addEventListener('blur', this._blur);
// restore tested set + best rollover
try {
const t = localStorage.getItem('keyboard-tester:tested');
if(t) this.tested = JSON.parse(t) || {};
const r = localStorage.getItem('keyboard-tester:maxRollover');
if(r) this.maxRollover = parseInt(r) || 0;
} catch(e){}
this.listening = true;
},
destroy(){
window.removeEventListener('keydown', this._down);
window.removeEventListener('keyup', this._up);
window.removeEventListener('blur', this._blur);
},
// ---- the full set of codes we expect a 100%-complete keyboard to cover ----
get allCodes(){
const codes = [];
this.rows.forEach(r => r.forEach(k => { if(k.c) codes.push(k.c); }));
this.navRows.forEach(r => r.forEach(k => { if(k.c && !k.spacer) codes.push(k.c); }));
if(this.numpadEnabled){
['NumLock','NumpadDivide','NumpadMultiply','NumpadSubtract','NumpadAdd',
'NumpadEnter','NumpadDecimal','Numpad0','Numpad1','Numpad2','Numpad3',
'Numpad4','Numpad5','Numpad6','Numpad7','Numpad8','Numpad9'].forEach(c => codes.push(c));
}
return codes;
},
get testedCount(){
const all = this.allCodes;
let n = 0;
all.forEach(c => { if(this.tested[c]) n++; });
return n;
},
get totalCount(){ return this.allCodes.length; },
get progressPct(){
if(!this.totalCount) return 0;
return Math.round((this.testedCount / this.totalCount) * 100);
},
get untestedList(){
return this.allCodes.filter(c => !this.tested[c]);
},
get heldCount(){
return Object.keys(this.held).filter(k => this.held[k]).length;
},
onDown(e){
// never let the page scroll / lose focus while testing
const block = ['Space','Tab','Backspace','ArrowUp','ArrowDown','ArrowLeft','ArrowRight',
'PageUp','PageDown','Home','End','Enter','/','\\''];
// also swallow lone F-keys (F1 help, F5 refresh…) so they're testable;
// modifier combos (Ctrl+C, Ctrl+F, Ctrl+Tab…) pass through to the browser
const loneFKey = /^F([1-9]|1[0-2])$/.test(e.code) && !e.ctrlKey && !e.metaKey && !e.altKey;
if(block.includes(e.code) || block.includes(e.key) || loneFKey) e.preventDefault();
const code = e.code || ('Key' + (e.key||'').toUpperCase());
this.last = {
key: e.key === ' ' ? 'Space' : e.key,
code: e.code || '(none)',
keyCode: e.keyCode,
which: e.which,
location: e.location,
repeat: e.repeat
};
if(typeof e.getModifierState === 'function'){
this.capsOn = e.getModifierState('CapsLock');
}
if(e.repeat){ return; } // held-key auto-repeat: state already set
// key-chatter detection: a fresh keydown within ~35ms of the same key's
// release is almost never human — it's a worn switch double-triggering
if(code){
const lu = this._lastUpAt[code];
if(lu && (performance.now() - lu) < 35){
this.chatter[code] = (this.chatter[code] || 0) + 1;
}
}
// mark held + tested
if(code){
this.held[code] = true;
if(!this.tested[code]){
this.tested[code] = true;
this.persist();
if(window.WD && WD.sound) WD.sound.play('pop');
if(window.WD && WD.haptic) WD.haptic(6);
} else {
if(window.WD && WD.sound) WD.sound.play('click');
}
}
this.presses++;
// rollover / ghosting: track max simultaneous physical keys
const h = this.heldCount;
if(h > this.maxRollover){
this.maxRollover = h;
try { localStorage.setItem('keyboard-tester:maxRollover', String(h)); } catch(_){}
}
// celebrate full completion once
if(this.testedCount >= this.totalCount && !this.everCelebrated){
this.everCelebrated = true;
const el = this.$refs.kb;
if(window.WD){
if(WD.celebrate){
const r = el ? el.getBoundingClientRect() : {left:innerWidth/2,top:innerHeight/2,width:0,height:0};
WD.celebrate(r.left + r.width/2, r.top + r.height/2);
} else if(WD.confetti){ WD.confetti(innerWidth/2, innerHeight/2); }
if(WD.toast) WD.toast('Every key tested! 🎉');
}
}
},
onUp(e){
const code = e.code || ('Key' + (e.key||'').toUpperCase());
if(code){ this.held[code] = false; this._lastUpAt[code] = performance.now(); }
if(typeof e.getModifierState === 'function'){
this.capsOn = e.getModifierState('CapsLock');
}
},
clearHeld(){ this.held = {}; },
persist(){
try { localStorage.setItem('keyboard-tester:tested', JSON.stringify(this.tested)); } catch(e){}
},
keyClass(k){
if(k.spacer) return 'opacity-0 pointer-events-none';
let base = 'kt-key';
if(this.held[k.c]) base += ' kt-held';
else if(this.tested[k.c]) base += ' kt-tested';
return base;
},
// tap support for touch devices — simulate a press of a given code
tap(k){
if(k.spacer || !k.c) return;
this.last = { key: k.l, code: k.c, keyCode: '(tap)', which:'(tap)', location: '-', repeat:false };
if(!this.tested[k.c]){
this.tested[k.c] = true;
this.persist();
if(window.WD && WD.sound) WD.sound.play('pop');
if(window.WD && WD.haptic) WD.haptic(6);
} else if(window.WD && WD.sound){ WD.sound.play('click'); }
this.presses++;
if(this.testedCount >= this.totalCount && !this.everCelebrated){
this.everCelebrated = true;
if(window.WD){ if(WD.confetti) WD.confetti(innerWidth/2, innerHeight/2); if(WD.toast) WD.toast('Every key tested! 🎉'); }
}
},
reset(){
this.tested = {};
this.held = {};
this.chatter = {};
this._lastUpAt = {};
this.last = null;
this.presses = 0;
this.everCelebrated = false;
try { localStorage.removeItem('keyboard-tester:tested'); } catch(e){}
if(window.WD && WD.sound) WD.sound.play('click');
if(window.WD && WD.toast) WD.toast('Cleared — start pressing keys');
},
resetRollover(){
this.maxRollover = 0;
try { localStorage.removeItem('keyboard-tester:maxRollover'); } catch(e){}
},
rolloverLabel(){
const n = this.maxRollover;
if(n >= 6) return 'N-key rollover (excellent)';
if(n >= 4) return n + '-key rollover (good)';
if(n >= 2) return n + '-key rollover';
if(n === 1) return '1 key — press more at once';
return 'press several keys together';
}
}" x-init="init()" class="select-none">
Tested
/
Held now
Max rollover
Presses
All-keys progress
code=keyCode=which=location=held / repeatingCaps Lock ON
Press any key — the on-screen keyboard lights up and stays marked as tested. Hold several keys at once to check for ghosting.
Anti-ghosting: — down right now
⚡ Possible key chatter:These keys re-triggered within 35 ms of being released — a classic sign of a worn switch that "double-types". Press them slowly a few times to confirm.
Still untested ()
🎉 Every key registered — your keyboard checks out!
100% client-side — nothing is sent anywhere. Touch devices: tap keys to mark them. Tip: hold 6+ keys at once to verify full N-key rollover.
Why you’ll love Keyboard Tester
⚡Instant & freeNo signup, no paywall, no limits — Keyboard Tester works the moment the page loads.
🔒Completely privateEverything runs in your browser. Nothing you enter is ever uploaded or stored on a server.
📱Works on any deviceFully responsive and touch-friendly — use it on your phone, tablet or desktop.
📱Uses your real deviceTaps into your actual camera, mic or motion sensors — right in the browser.
How to use it
01
Open it
No download and no login — the tool is ready right at the top of this page.
02
Use it
Enter your details or start interacting. Everything updates live as you go.
03
Get your result
Copy, download or share your result in a single tap. That’s it.
About Keyboard Tester
Press any key and the on-screen keyboard lights it up while it is held, then keeps it marked as tested — so you can methodically work across a full board, including the navigation cluster, arrow keys and an optional numpad, until the progress bar hits 100%. A live readout shows exactly what the browser received for each press: the key character, the physical code, the legacy keyCode and which values, the key location, and whether Caps Lock is on. Everything runs client-side; no keystroke leaves your device, and your tested set is saved locally so you can come back and finish later.
Beyond "does every key work", the tester checks two hardware faults that are hard to spot otherwise. The first is rollover and ghosting: cheap membrane keyboards wire keys in a matrix that can only register a few simultaneous presses, so fast gaming combos silently drop keys. Hold several keys at once and the max-rollover stat records how many registered together — six or more at once is effectively N-key rollover.
The second is key chatter, the classic symptom of a worn or dirty mechanical switch that "double-types" letters. The tester timestamps every release, and if the same key fires a fresh keydown within 35 milliseconds of being let go — far faster than a human can genuinely re-press — it flags that key with a chatter count so you know which switch to clean or replace.
Popular uses
✦ Check every key on a used or second-hand keyboard before buying it
✦ Diagnose double-typing letters with the key-chatter detector
✦ Verify a gaming keyboard's N-key rollover claim by holding 6+ keys
✦ Document which keys died after a spill before deciding on a repair
✦ Look up JavaScript key and code values while building keyboard shortcuts
Frequently asked questions
What is keyboard ghosting and how do I test for it here?
Ghosting happens when a keyboard's internal matrix cannot distinguish certain combinations, so pressing a third or fourth key either does not register or registers a phantom key you never pressed. To test it, hold a realistic gaming combo — W, A, S, D plus Space and Shift — and watch the "Held now" and "Max rollover" stats. If keys stop lighting up while physically held, you have found your board's rollover limit; 6-key or higher is excellent.
What does the key chatter warning mean?
Chatter (also called switch bounce) is when a worn, dirty or defective switch electrically re-triggers on a single press, typing "tthe" when you typed "the". The tester flags any key that sends a new keydown within 35 ms of its own release — an interval no human re-press achieves. Press the flagged key slowly several times to confirm; if it keeps chattering, the usual fixes are compressed air, contact cleaner, or replacing that switch.
What is the difference between key, code and keyCode in the readout?
code identifies the physical key position and ignores your layout — the key right of Tab is always KeyQ, even on an AZERTY keyboard where it types "a". key is the actual character or action your layout produces, so it changes with language and Shift state. keyCode and which are deprecated numeric IDs kept only for legacy scripts; new code should use key or code, which is why this tester shows all of them side by side.
Why do some keys, like Fn, never register?
The Fn key is handled entirely inside the keyboard's own controller and never sends an event to the operating system, so no browser tool can see it — only its effect on other keys. A few other keys can be swallowed before reaching the page too: the OS may reserve certain shortcuts, and the tester deliberately lets modifier combos like Ctrl+C pass through to the browser while capturing lone presses of everything on the board.
Can I use this to check a laptop or second-hand keyboard for dead keys?
Yes — that is the main job. Work through the board and the "Still untested" chips list exactly which keys have never registered, which is the fastest way to document dead keys after a spill or before buying a used keyboard. Progress is saved in your browser, so you can clean the keyboard, come back, and re-press only the problem keys. On touch devices you can tap on-screen keys to mark them manually.
Is Keyboard Tester free to use?
Keyboard Tester is 100% free — no signup, no watermarks and no usage limits. It’s one of 200+ free tools we build and give away.
Is my camera or mic data uploaded?
No. All sensor data is processed live on your device and never leaves it or gets stored.