Hangman Word Guessing Game — Free HTML CSS JS Snippet
Hangman Word Guessing Game · Games · Plain HTML, CSS & JS · Live preview
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
Hangman Word Guessing Game — SVG Gallows Reveal, On-Screen Keyboard & Word State Machine

Hangman is one of the oldest word-guessing games, and its core mechanic translates cleanly into a small, self-contained state machine: a hidden word, a limited number of wrong guesses, and a visual penalty that escalates with every mistake. This snippet builds a fully playable version with a real SVG-drawn gallows figure that reveals one new body part per wrong guess, a clickable on-screen QWERTY keyboard, and full physical-keyboard support, all driven by a small set of JavaScript functions with no external word-guessing library.
The gallows as layered, pre-drawn SVG
Rather than drawing the hangman figure with JavaScript canvas calls or swapping image files, the entire gallows and figure are pre-drawn once as a single SVG containing eight separate shape elements: the gallows post and beam (always visible), then seven body-part elements — the rope, head, body, both arms, and both legs — each given the class hm-part and starting at opacity: 0. A PART_ORDER array in JavaScript lists these seven part class names in the exact sequence they should appear. Every time a wrong guess is made, revealBodyPart(wrongCount - 1) looks up the next class name in that array and adds a .show class, which flips opacity to 1 with a CSS transition. This means the entire drawing logic is just an array index lookup plus a class toggle — no path recalculation, no redraw, and the reveal animation is handled entirely by the CSS transition: opacity 0.25s ease rule.
Word state and letter-slot rendering
The secret word is chosen once per game with WORDS[Math.floor(Math.random() * WORDS.length)] from a small hardcoded word list, and correctly-guessed letters are tracked in a Set called guessedLetters — a Set is used specifically because it gives O(1) has() lookups and naturally prevents duplicate guesses from being counted twice. renderWord() rebuilds the row of letter slots on every guess: it iterates each character of the secret word and, for characters present in guessedLetters, renders the actual letter with a .revealed class that switches its underline to the accent colour; otherwise it renders an underscore placeholder. Because a Set lookup is used rather than tracking revealed positions directly, a single correct guess of a repeated letter (for example guessing "A" in "GALAXY") reveals every occurrence of that letter simultaneously, exactly as the physical game works.
Dual input: on-screen keyboard and real keyboard
The on-screen keyboard is generated programmatically by looping character codes 65 through 90 (A-Z) and creating one button per letter, each wired to guessLetter(letter) via addEventListener. A parallel keydown listener on document normalises e.key to uppercase and, if it is a single A-Z character, calls the exact same guessLetter() function — so physical typing and on-screen clicking share one code path with no duplicated logic. Once a letter has been guessed, its corresponding button gets disabled = true plus either a .correct (green) or .wrong (red) class, which both greys it out via reduced opacity and blocks further clicks or accidental re-guesses of the same letter from the keyboard listener, since guessedLetters.has(letter) short-circuits guessLetter() at the top.
Win and loss detection
After every guess, checkGameEnd() runs two checks: [...secretWord].every(ch => guessedLetters.has(ch)) tests whether every unique character in the word has been guessed (a win), and wrongCount >= MAX_WRONG (set to 7, matching the seven gallows parts revealed after the always-visible frame) tests for a loss. On a loss, revealWordFully() forcibly renders every letter of the secret word regardless of what was guessed, so the player sees the answer. Either outcome disables the entire keyboard and reveals the "Play Again" button, which calls newGame() to pick a fresh random word, reset guessedLetters, hide all gallows parts, and rebuild the keyboard from scratch.
Step by step
How to Use
- 1Guess a letterClick any letter on the on-screen keyboard, or simply type on your physical keyboard — the document-level keydown listener normalises input and calls the same guessLetter() function either way.
- 2Watch correct guesses reveal instantlyA correct letter reveals every matching position in the word at once via the guessedLetters Set lookup in renderWord(), and the corresponding on-screen key turns green and becomes disabled.
- 3Track wrong guesses on the gallowsEach wrong guess increments wrongCount, updates the "X / 7 wrong guesses" counter, and calls revealBodyPart() to fade in the next SVG body part — rope, head, body, arm, arm, leg, leg — in that fixed order.
- 4Reach a win or loss stateGuessing every letter in the word before reaching 7 wrong guesses triggers a win message; reaching 7 wrong guesses first triggers a loss message and forcibly reveals the full word via revealWordFully().
- 5Start a new roundClick "Play Again" to call newGame(), which picks a new random word from the WORDS array, clears guessedLetters, resets wrongCount to zero, hides all gallows parts, and rebuilds a fully enabled keyboard.
- 6Expand the word listAdd or replace entries in the WORDS array at the top of the JS panel — keep them uppercase since guessLetter() compares against uppercase input, or add a themed category selector that swaps in a different array before calling newGame().
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
A PART_ORDER array lists the CSS class names of the seven hangman parts in the exact sequence they should appear (rope, head, body, both arms, both legs). Each wrong guess calls revealBodyPart(wrongCount - 1), which looks up PART_ORDER at that index and adds a .show class to the matching SVG element, flipping its opacity from 0 to 1 via a CSS transition — no coordinate math or redrawing is involved.
A Set gives constant-time has() lookups, which keeps duplicate-guess checking and word-completion checking fast regardless of word length, and it inherently rejects duplicate values so the same letter can never be counted twice even if guessLetter() were somehow called twice with the same letter.
Yes. A single keydown listener on document normalises e.key to uppercase and, if it is a letter A-Z, calls the exact same guessLetter() function that the on-screen keyboard buttons call. There is no separate code path for physical typing, so behaviour (including duplicate-guess prevention and disabling used keys visually) stays consistent between both input methods.
Seven, defined by the MAX_WRONG constant, which matches the seven SVG parts revealed after the always-visible gallows frame (rope, head, body, two arms, two legs). Change MAX_WRONG to make the game easier or harder — note you would also need to adjust PART_ORDER if you change the total number of visual stages.
Yes — replace the single flat WORDS array with an object of categories, for example { easy: [...], hard: [...] }, add a category selector to the HTML, and update newGame() to pick a random word from the currently selected category array instead of the single WORDS constant. The rest of the game logic (guessing, rendering, win/loss detection) needs no changes.