Fun & Developer Toys
MINIMAL WPM SPEED TYPER
Test your typing speed and accuracy. Mechanical key click sounds included. Click box and start typing!
0
WPM
100%
Accuracy
30s
Time Remaining
Developer Reference
Core Algorithm & Standalone Script
Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.
const SAMPLE_TEXT = "the quick brown fox jumps over the lazy dog quantum mechanics describes nature at the scale of atoms and subatomic particles speed typing improves muscle memory and developer productivity";
let words = [];
let charIndex = 0;
let errors = 0;
let timer = null;
let timeLeft = 30;
let isRunning = false;
const wordsWrap = document.getElementById('words-wrap');
const input = document.getElementById('type-input');
const wpmEl = document.getElementById('stat-wpm');
const accEl = document.getElementById('stat-acc');
const timeEl = document.getElementById('stat-time');
const soundChk = document.getElementById('chk-sound');
let audioCtx = null;
function playKeyClick() {
if (!soundChk.checked) return;
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
if (audioCtx.state === 'suspended') audioCtx.resume();
const now = audioCtx.currentTime;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(600 + Math.random() * 200, now);
gain.gain.setValueAtTime(0.08, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.03);
osc.connect(gain); gain.connect(audioCtx.destination);
osc.start(now); osc.stop(now + 0.03);
}
function initTest() {
clearInterval(timer);
timeLeft = 30;
charIndex = 0;
errors = 0;
isRunning = false;
wpmEl.textContent = '0';
accEl.textContent = '100%';
timeEl.textContent = '30s';
input.value = '';
wordsWrap.innerHTML = '';
SAMPLE_TEXT.split('').forEach((ch, idx) => {
const span = document.createElement('span');
span.className = 'char' + (idx === 0 ? ' current' : '');
span.textContent = ch;
wordsWrap.appendChild(span);
});
}
function startTimer() {
isRunning = true;
timer = setInterval(() => {
timeLeft--;
timeEl.textContent = timeLeft + 's';
if (timeLeft <= 0) {
clearInterval(timer);
isRunning = false;
input.blur();
}
}, 1000);
}
document.getElementById('typer-card').addEventListener('click', () => input.focus());
input.addEventListener('input', e => {
if (!isRunning && timeLeft > 0) startTimer();
playKeyClick();
const chars = wordsWrap.querySelectorAll('.char');
const val = input.value;
const currentCh = val[val.length - 1];
if (charIndex < chars.length && timeLeft > 0) {
chars.forEach(c => c.classList.remove('current'));
if (currentCh === chars[charIndex].textContent) {
chars[charIndex].classList.add('correct');
} else {
chars[charIndex].classList.add('wrong');
errors++;
}
charIndex++;
if (charIndex < chars.length) chars[charIndex].classList.add('current');
// WPM & Acc
const elapsed = (30 - timeLeft) / 60 || 0.01;
const wpm = Math.round((charIndex / 5) / elapsed);
const accuracy = Math.max(0, Math.round(((charIndex - errors) / charIndex) * 100)) || 100;
wpmEl.textContent = isNaN(wpm) ? 0 : wpm;
accEl.textContent = accuracy + '%';
}
});
document.getElementById('btn-restart').addEventListener('click', initTest);
initTest();