Freedom Sale
Independence Day Special — Unlock the AI Path 70% off our most popular AI course · Limited time offer
--Days
--Hrs
--Min
--Sec
Claim Your Discount
✦ Beginner ⏱ 35 min

⌨️ Build a Typing Speed Test Web App

🎯 What You'll Build

A typing speed test that measures your WPM and accuracy in real time — with character-level highlighting, a 60-second countdown, and a results screen.

📋 What You'll Need

1

HTML layout

The test shows a passage to type, an input field, live stats, and a timer.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Typing Speed Test</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>

<?php require_once __DIR__ . '/../promo_banner.php'; ?>

  <div class="app">
    <h1>⌨️ Typing Speed Test</h1>
    <div class="stats-bar">
      <div class="stat"><div class="stat-val" id="wpm">0</div><div class="stat-lbl">WPM</div></div>
      <div class="stat"><div class="stat-val" id="accuracy">100</div><div class="stat-lbl">% Accuracy</div></div>
      <div class="stat"><div class="stat-val" id="timer">60</div><div class="stat-lbl">Seconds</div></div>
    </div>

    <div class="passage" id="passage"></div>
    <textarea id="inputBox" placeholder="Click here and start typing..." rows="4" disabled></textarea>

    <button id="startBtn" onclick="startTest()">Start Test</button>
    <div class="result hidden" id="result"></div>
  </div>
  <script src="app.js"></script>
</body>
</html>
2

Passage rendering with per-character spans

Wrap each character in a span so we can colour correct/incorrect letters in real time.

// app.js
const PASSAGES = [
  "The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs.",
  "Success is not final, failure is not fatal: it is the courage to continue that counts.",
  "Python is a versatile programming language used for web development, data science, and automation.",
  "In the beginning was the Word, and the Word was with God, and the Word was God.",
  "To be or not to be, that is the question. Whether tis nobler in the mind to suffer the slings and arrows.",
];

let passage, typed = '', startTime, countdown, timeLeft = 60, active = false;

function renderPassage() {
  passage = PASSAGES[Math.floor(Math.random() * PASSAGES.length)];
  const el = document.getElementById('passage');
  el.innerHTML = passage.split('').map((ch, i) =>
    `<span id="c${i}">${ch === ' ' ? '&nbsp;' : ch}</span>`
  ).join('');
}

function colourChars(input) {
  for (let i = 0; i < passage.length; i++) {
    const span = document.getElementById(`c${i}`);
    if (i < input.length) {
      span.className = input[i] === passage[i] ? 'correct' : 'wrong';
    } else if (i === input.length) {
      span.className = 'cursor';
    } else {
      span.className = '';
    }
  }
}
3

Timer, WPM calculation, and results

Start the 60-second timer on first keypress and calculate WPM every second.

function startTest() {
  renderPassage();
  const box = document.getElementById('inputBox');
  box.value = ''; box.disabled = false; box.focus();
  timeLeft = 60; active = false;
  document.getElementById('result').classList.add('hidden');
  document.getElementById('startBtn').textContent = 'Restart';

  box.oninput = () => {
    if (!active) {
      active = true;
      startTime = Date.now();
      countdown = setInterval(tick, 1000);
    }
    typed = box.value;
    colourChars(typed);
    updateStats();

    if (typed === passage) endTest();
  };
}

function tick() {
  timeLeft--;
  document.getElementById('timer').textContent = timeLeft;
  updateStats();
  if (timeLeft <= 0) endTest();
}

function updateStats() {
  const mins = (Date.now() - startTime) / 60000;
  const words = typed.trim().split(/\s+/).filter(Boolean).length;
  const wpm   = mins > 0 ? Math.round(words / mins) : 0;

  let correct = 0;
  for (let i = 0; i < typed.length; i++) {
    if (typed[i] === passage[i]) correct++;
  }
  const acc = typed.length > 0 ? Math.round(correct / typed.length * 100) : 100;

  document.getElementById('wpm').textContent      = wpm;
  document.getElementById('accuracy').textContent = acc;
}

function endTest() {
  clearInterval(countdown);
  document.getElementById('inputBox').disabled = true;
  const wpm = document.getElementById('wpm').textContent;
  const acc = document.getElementById('accuracy').textContent;
  const grade = wpm >= 80 ? '🏆 Expert' : wpm >= 60 ? '⭐ Advanced' : wpm >= 40 ? '👍 Average' : '📚 Keep Practising';
  const result = document.getElementById('result');
  result.innerHTML = `<b>${grade}</b> — ${wpm} WPM · ${acc}% accuracy`;
  result.classList.remove('hidden');
}
💡 Tip: Average typing speed is 40 WPM. Touch typists average 60-80 WPM. Professional transcriptionists average 90-100 WPM. The world record is over 200 WPM.

🎉 You Did It!

A fully functional typing test in under 120 lines. The per-character colouring technique is the same approach used by Monkeytype and TypeRacer.

Found something wrong?

Spotted a bug, broken code, or something that doesn't look right? Tell us what's off and we'll fix it.