For supporters
Source Code
Want the source code?
Play the game for free. Become a WebDevPuneet supporter to unlock the complete HTML, CSS and JavaScript source code, the exports (HTML, React, Vue, Angular, Tailwind) and Fork.
Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Word Ladder Game — One-Letter Transformation Puzzle with Diff Highlighting and Dictionary Validation

A word ladder (also called a doublet or word golf) is a classic word puzzle invented by Lewis Carroll: transform a start word into an end word by changing exactly one letter at a time, with every intermediate step required to be a real word of the same length. This snippet implements a fully playable version — an input form, a running trail of accepted steps, per-letter diff highlighting, move counting, and a small built-in dictionary used to validate every guess — without any backend or external word-list API.
Representing a puzzle as a chain plus an accepted dictionary
Each entry in the PUZZLES array bundles two things: a known-solvable chain from the start word to the end word (used only to know the target and to power the hint button), and a small dict array of every word the puzzle will accept as a valid intermediate step, including the chain words themselves. Keeping the dictionary intentionally small and puzzle-specific — rather than shipping a full English word list — keeps the snippet self-contained while still letting players find alternate valid ladders beyond the exact one baked into chain, since any accepted word from dict that satisfies the one-letter rule is a legal move.
Validating a guess: length, single-letter diff, dictionary membership, no repeats
submitGuess() runs a guess through four checks in order before accepting it. First, the guessed word's length must match the current word's length — a word ladder never changes word length mid-chain. Second, diffCount() compares the guess to the current word character-by-character and counts how many positions differ; the guess is rejected unless exactly one letter changed, which is the entire rule that makes the puzzle a "ladder" rather than free-form guessing. Third, the guess must appear in the puzzle's dict Set (uppercased for case-insensitive matching) — this stops nonsense strings that happen to differ by one letter from being accepted as real words. Fourth, a repeat check via trail.includes(word) prevents padding the ladder by bouncing back and forth between two words already used.
The diff-highlighting trail
renderTrail() rebuilds the visible list of accepted words after every successful move. For every word after the first, it compares each character position against the previous word in the trail and wraps any differing character in a .diff-letter span, which is styled in the accent color. This turns an otherwise flat list of words into a readable record of exactly which letter changed at each step, reinforcing the puzzle's core mechanic visually rather than just verbally.
Move counting and a persisted best score
A moves counter increments on every accepted step and is shown live in the stats header. When the current word matches the puzzle's final target word, solved is set to true, the input is disabled, and if this run's moves total is lower than any previous best for the session, best updates — giving returning players on the same puzzle set a concrete number to beat, purely from in-memory state (no localStorage is used, so best resets on page reload).
Hints without giving away the whole solution
The hint button does not reveal the full chain at once. It looks at trail.length — the number of words already accepted, including the start word — and reveals only chain[trail.length], i.e. the next single step in the reference solution. Because players can legally deviate from the reference chain (any dictionary word satisfying the one-letter rule is accepted), the hint is best read as "a valid next word," not "the only correct next word."
Step by step
How to Use
- 1Read the goal wordsThe header shows a start word and an end word, e.g. COLD to WARM. Every step in between must change exactly one letter and be a real word.
- 2Type your next wordType a word into the input and submit it. submitGuess() checks it changes exactly one letter from the current last word in the trail, via diffCount().
- 3Watch the diff highlightingEach accepted word in the trail highlights the letter that changed from the previous word, so you can trace exactly how the chain evolved.
- 4Reach the end word to winOnce your guess exactly matches the target end word, the puzzle is marked solved, the input disables, and your move count is compared against the best score for that puzzle.
- 5Ask for a hint if stuckClick "Reveal a hint" to see chain[trail.length] — one valid next word from the reference solution, without spoiling the rest of the ladder.
- 6Load a new puzzleClick "New puzzle" to cycle to the next entry in the PUZZLES array and reset moves, trail and best-score tracking for that puzzle.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
The guessed word must be the same length as the current word, differ from it in exactly one letter position (checked by diffCount()), appear in the puzzle's built-in dict Set, and not already appear earlier in the trail. All four conditions must pass for the move to be accepted.
No. Any sequence of accepted dictionary words that satisfies the one-letter-change rule at every step and eventually reaches the exact end word counts as solved — the reference chain array is only used to determine the target word and to power the hint button, not to restrict your path.
Keeping dict scoped to each puzzle keeps the snippet fully self-contained with no external word-list API or large bundled file, while still allowing several valid alternate routes through the puzzle beyond the single baked-in chain.
The hint button looks up chain[trail.length] — using how many words you have accepted so far as an index — and reveals only that one next reference word, never the full remaining chain in one click.
Yes. Add an object to the PUZZLES array with a chain array (a known valid start-to-end sequence) and a dict array containing every word you want accepted as valid for that puzzle, including all words in chain.
No, best is held in a plain JavaScript variable for the current session only. To persist it across visits, read and write the value to localStorage inside loadPuzzle() and after a puzzle is solved.