You Might Also Like
Typing Speed Test WPM Counter — HTML CSS JS Snippet
Typing Speed Test (WPM Counter) · Forms · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Typing Speed Test WPM Counter — Live Character Diffing, Words-Per-Minute Calculation & Accuracy Scoring

Typing speed tests are a deceptively small feature that requires getting several interconnected pieces of logic right at once: character-by-character comparison against a target string, a words-per-minute formula that only starts counting from the user's first keystroke, an accuracy percentage that updates live, and a completion check that triggers the moment the typed text exactly matches the target. This snippet implements all of it in vanilla JavaScript with no dependencies, using the same core technique that speed-typing sites like MonkeyType and 10FastFingers rely on: comparing two strings index by index and re-rendering the sample text with per-character CSS classes on every keystroke.
Character-by-character diffing
The heart of the snippet is renderTarget(typed), which loops over every character of the target sentence and compares it against the corresponding character the user has typed so far. Each target character is wrapped in its own <span> and assigned one of three classes: char-correct (green) if typed[i] === target[i], char-incorrect (red background) if the user typed something different at that position, or no class at all if the user has not reached that character yet — except for the very next character, which gets char-current to show a blinking-style underline cursor indicator. If the user has typed more characters than the target sentence contains (for example, extra characters at the end), those are appended in a separate char-extra span with a red underline, so over-typing is visually distinct from a wrong-character mismatch rather than silently ignored. This span-per-character approach means the entire sample re-renders on every input event, which is cheap enough at typical sentence lengths (60-90 characters) to run smoothly on every keystroke without any debouncing.
The words-per-minute formula
WPM is calculated using the standard typing-test convention: one "word" equals five characters (including spaces), regardless of actual word boundaries, because this normalizes scoring across sentences with different average word lengths. The formula is words = typed.length / 5, then wpm = Math.round((typed.length / 5) / (elapsedSeconds / 60)). Critically, the timer does not start when the component loads — it starts on the very first input event via a null-check on startTime, matching how real typing tests behave (you should not be penalized for time spent reading the prompt before you start typing). A setInterval running every 100ms recalculates and redisplays the live WPM, accuracy, and elapsed time while the test is in progress, so numbers update smoothly rather than jumping only on keystrokes.
Accuracy scoring and completion detection
computeAccuracy(typed) counts how many typed characters match the target at the same index, divided by the total number of characters typed so far (not the target length), so accuracy reflects the quality of what has actually been typed at any moment — including dips caused by mistakes that were later corrected. The test only completes when typed.length >= target.length && typed === target, an exact string equality check, meaning a single leftover typo blocks completion until the user backspaces and fixes it, mirroring the "you cannot finish with mistakes present" behavior of most typing test tools. On completion, finishTest() stops the interval timer, disables the textarea so no further input is registered, and reveals a results overlay summarizing final WPM, accuracy, and total time.
Random sample rotation and reset flow
Five sample sentences of varying length and vocabulary live in a SAMPLES array; pickSample() selects one at random using Math.floor(Math.random() * SAMPLES.length). Both the header "Try Again" button and the results overlay's "Try Again" button call the same resetTest() function, which clears the interval, resets startTime to null, re-enables the textarea, picks a fresh random sample, and re-renders the target text in its untyped state — ensuring every attempt starts from a clean, consistent baseline with no leftover timer or stale DOM classes from the previous run.
Build with AI
Build, Understand, Optimize, and Extend It With AI
Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to walk through exactly how renderTarget() builds the per-character span markup on every keystroke, and how the WPM formula and the null-startTime check work together to make sure timing only begins once real typing starts. It's also worth asking the assistant to extend the test meaningfully: request a fixed-duration mode (for example a 60-second countdown where the test ends automatically regardless of completion), a per-word accuracy breakdown highlighting which specific words caused the most retyping, or a results history stored in localStorage so returning users can see their WPM trend over multiple sessions. Because the character-diffing and timing logic are cleanly separated from the DOM rendering, it's a good snippet to ask an assistant to refactor into a reusable typing-test engine you could reuse in other contexts.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a typing speed test in plain HTML, CSS, and JavaScript that measures words-per-minute and accuracy in real time as the user types — no external libraries or fonts.
Requirements:
- Display a sample sentence and provide a text input where the user types it; on every keystroke, re-render the sample text so each character is individually marked correct, incorrect, or not-yet-typed compared against the same-index character the user has typed so far.
- Handle the case where the user types more characters than the sample sentence contains by visually flagging the extra characters distinctly, rather than silently ignoring or crashing on them.
- Start a timer precisely on the user's first keystroke (not on page load), and calculate a live words-per-minute value using the standard convention of one word equaling five characters, updating continuously (for example every 100ms) rather than only on each keystroke.
- Calculate and display a live accuracy percentage based on how many of the characters typed so far exactly match the target at their position.
- Detect completion only when the typed text exactly matches the full target sentence (including catching any leftover uncorrected mistakes), and at that point stop the timer, disable further input, and show a results summary of final WPM, accuracy, and total elapsed time.
- Provide a "Try again" control that resets all state — timer, input value, disabled state, and displayed stats — and selects a new random sentence from a pool of at least four sample sentences of varying length.
- Make sure rapid typing does not cause any visual lag or flicker in the character highlighting, and that backspacing correctly reverts characters from correct/incorrect back to their untyped appearance.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Start typing to beginClick into the textarea and start typing the displayed sentence. The timer starts automatically on your first keystroke via the null-check on startTime inside handleInput() — there is no separate start button.
- 2Watch live character feedbackAs you type, renderTarget() re-renders the sample sentence on every input event, coloring correct characters green (.char-correct) and incorrect ones red (.char-incorrect). Extra characters typed beyond the sentence length appear underlined in red via .char-extra.
- 3Monitor your live WPM and accuracyThe three stat tiles above the sentence update every 100ms via a setInterval calling updateLiveStats(), showing your current words-per-minute, accuracy percentage, and elapsed time in real time as you type.
- 4Complete the test and view resultsFinish typing the full sentence exactly as shown — the test only completes when your typed text exactly equals the target string. finishTest() then disables the textarea and reveals an overlay with your final WPM, accuracy, and total time.
- 5Try again with a new random sentenceClick "Try Again" (either in the footer or on the results overlay) to call resetTest(), which picks a new random sentence from the SAMPLES array, clears the timer, and re-enables the textarea for a fresh attempt.
- 6Add your own sample sentencesEdit the SAMPLES array at the top of the JS panel to add, remove, or replace sentences. Longer or more complex sentences with punctuation and varied word lengths make for a more challenging or realistic typing test.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Standard typing test convention (used by MonkeyType, 10FastFingers, and most typing tutors) defines one "word" as five characters including spaces, regardless of actual word boundaries. This normalizes the WPM score across sentences with different average word lengths — a sentence full of short words and one full of long words produce comparable, fair WPM figures under this formula, rather than actual word-counting which would unfairly reward or penalize vocabulary choice.
The timer starts on the very first input event where startTime is still null, captured as Date.now() at that moment — not when the page loads or the textarea is focused. This means time spent reading the sentence before typing does not count against you. The timer stops the instant the typed text exactly equals the target string, at which point finishTest() clears the interval and disables further input.
computeAccuracy() counts how many characters in the currently typed text match the target string at the same index, then divides by the total number of characters typed so far (not the target sentence length). This means accuracy reflects real-time quality of your current input — if you make a mistake and then backspace to fix it, accuracy recovers immediately since the incorrect character is no longer part of the typed string being measured.
Any characters typed beyond the target sentence's length are rendered in a separate span with the char-extra class, shown in red with an underline, so over-typing is visually distinguished from simply mistyping a character that exists in the target. Because completion requires exact string equality (typed === target), extra trailing characters will also prevent the test from completing until they are removed.
Yes, edit the SAMPLES array at the top of the JS panel — it is a plain array of strings, and pickSample() selects a random index from whatever length the array currently is via Math.floor(Math.random() * SAMPLES.length). You can add as many sentences as you want, including longer paragraphs, though very long text will make the sample-text panel taller and may need adjusted line-height or a max-height with scroll.