Source Code

<div class="game-card">
  <div class="game-header">
    <div class="game-title">
      <span class="game-icon">🔤</span>
      <h2>Word Unscramble</h2>
    </div>
    <button class="btn btn-ghost" id="btn-new-game">New game</button>
  </div>

  <div class="stat-row">
    <div class="stat-box">
      <span class="stat-label">Score</span>
      <span class="stat-value" id="stat-score">0</span>
    </div>
    <div class="stat-box">
      <span class="stat-label">Round</span>
      <span class="stat-value" id="stat-round">1</span>
    </div>
    <div class="stat-box">
      <span class="stat-label">Hints used</span>
      <span class="stat-value" id="stat-hints">0</span>
    </div>
  </div>

  <p class="clue-label" id="clue-label">Unscramble the letters below</p>

  <div class="letter-row" id="letter-row"></div>

  <div class="hint-row" id="hint-row"></div>

  <form class="guess-form" id="guess-form" autocomplete="off">
    <input type="text" id="guess-input" class="guess-input" placeholder="Type your guess..." maxlength="20" />
    <button type="submit" class="btn btn-primary" id="btn-submit">Submit</button>
  </form>

  <div class="action-row">
    <button class="btn btn-outline" id="btn-shuffle">
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><polyline points="16 3 21 3 21 8"/><line x1="4" y1="20" x2="21" y2="3"/><polyline points="21 16 21 21 16 21"/><line x1="15" y1="15" x2="21" y2="21"/><line x1="4" y1="4" x2="9" y2="9"/></svg>
      Shuffle
    </button>
    <button class="btn btn-outline" id="btn-hint">
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 12.7V17h8v-2.3A7 7 0 0 0 12 2z"/></svg>
      Hint
    </button>
    <button class="btn btn-primary" id="btn-next" disabled>Next word →</button>
  </div>

  <p class="feedback" id="feedback" aria-live="polite"></p>
</div>

Word Unscramble Puzzle Game — Free HTML CSS JS Snippet

Word Unscramble Puzzle Game · Games · Plain HTML, CSS & JS · Live preview

What's included

Features

Fisher-Yates shuffle: scrambleWord() produces a uniformly random letter permutation, retried if it matches the original word
Sequential word pool: shuffled once per pass via refillPool() so no word repeats until the full list has been seen
Hint system: hintedPositions Set reveals one letter at a time in a dedicated answer-key row without reordering the puzzle tiles
Dynamic scoring: Math.max(100 - hintsUsed * 25, 25) rewards hint-free solves while flooring the minimum per-round score
Cosmetic Shuffle button: re-scrambles the same target word for a fresh visual arrangement without affecting score or hints
CSS shake animation on incorrect guesses, forced reflow via offsetWidth so the animation restarts on repeated wrong answers
aria-live="polite" feedback region so screen readers announce correct/incorrect results automatically
New game control fully resets score, round count, and word pool for unlimited replayability

About this UI Snippet

Word Unscramble Puzzle Game — Letter Tiles, Scoring, Hints & Vanilla JS Word Logic

Screenshot of the Word Unscramble Puzzle Game snippet rendered live

Word unscramble puzzles are one of the oldest and most durable casual game formats because the core loop is instantly understandable — look at a jumble of letters, rearrange them mentally, type the word you see. This snippet implements a complete, replayable version of that loop entirely in vanilla JavaScript: a hardcoded word list, a real Fisher-Yates shuffle algorithm, a scoring system that rewards unaided solves, and a hint mechanic that trades points for help. There is no backend and no dependency — everything runs client-side against a plain JavaScript array.

How the scrambling actually works

The word pool is a flat array of 15 lowercase words of varying length (six to seven letters each, chosen so the puzzle is neither trivially short nor frustratingly long). scrambleWord() splits the target word into a character array and runs the classic Fisher-Yates shuffle: iterate from the last index down to the first, swapping each element with a randomly chosen earlier (or equal) element. This produces a mathematically uniform random permutation, unlike naive sort(() => Math.random() - 0.5) shuffles which are provably biased. Because a truly random shuffle can occasionally return the original word unchanged, the function retries up to ten times if the scrambled result matches the source word exactly.

Rounds, the word pool, and avoiding repeats

Rather than picking a random word from the full list on every round (which can repeat the same word twice in a row), the game shuffles the entire word list once per pass and walks through it sequentially with a poolIndex pointer. When the pointer reaches the end of the shuffled pool, refillPool() reshuffles the same 15 words into a fresh random order and resets the index to zero, so the game stays endlessly replayable while guaranteeing every word is seen once before any repeats.

The hint system and its effect on scoring

Clicking Hint does not spoil the puzzle outright — it reveals exactly one additional letter in its correct position, tracked in a hintedPositions Set keyed by character index. A separate row of boxes beneath the scrambled tiles mirrors the target word's length; unrevealed boxes stay blank while hinted ones display the letter with an accent-coloured underline. Crucially, the scrambled letter tiles themselves are never reordered by a hint — hints only affect the answer-key row, so the player still has to do the unscrambling work for the remaining letters. Because hintedPositions.size is read directly when a correct guess is submitted, scoring naturally and transparently reflects how much help was used.

Scoring formula and the shuffle button

A correct guess awards Math.max(100 - hintsUsed * 25, 25) points — a flawless, no-hint solve is worth 100 points, each hint costs 25, and the score floors at 25 so no correct answer is ever worth zero. The separate Shuffle button is purely cosmetic: it re-runs scrambleWord() on the same target word and re-renders the letter tiles in a new random order, giving players who feel visually "stuck" on one arrangement a fresh look at the same letters without changing the underlying answer or resetting any hint progress.

Feedback, state resets, and accessibility

Submitting the form (via the Submit button or pressing Enter) compares the trimmed, lowercased input against currentWord. A correct match adds a green .correct class to the input, disables further guessing, and reveals the full hint row as confirmation; an incorrect guess triggers a CSS shake keyframe animation on the input by removing and re-adding the class (forcing a reflow with void guessInput.offsetWidth so the animation restarts on consecutive wrong guesses) alongside a red error message. The aria-live="polite" attribute on the feedback paragraph means screen readers announce the result without needing focus to move. New game fully resets score, round count, and the word pool for a clean restart at any time.

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 trace exactly how scrambleWord(), pickWord(), and the hintedPositions Set work together across a full round — from generating the scramble to scoring a correct guess. It's a good candidate for extension: ask the assistant to add a countdown timer per round for a speed-run mode, persist the best score to localStorage the way a high-score table would, or replace the flat hardcoded WORDS array with categorised word lists (animals, countries, tech terms) selectable from a dropdown. You could also ask it to review whether the Fisher-Yates shuffle implementation is correctly unbiased, or to add a "reveal answer" button that ends the round without awarding points, which is a common feature in commercial word-game apps. Treat the code as a working starting point to interrogate and reshape, not a finished, untouchable artifact — the assistant can explain any part of the state management or propose a cleaner structure if you plan to port it into a React component with hooks instead of plain DOM manipulation.

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:

text
Build a word unscramble puzzle game in plain HTML, CSS, and JavaScript with scoring, hints, and a shuffle-only-cosmetic control — no frameworks or libraries.

Requirements:
- A hardcoded array of 10-15 target words of varying length, with a genuinely unbiased shuffle algorithm (Fisher-Yates, not a naive sort-based shuffle) used both to scramble each word's letters and to randomize the order words are presented in across a full pass of the list before any repeats occur.
- A text input where the player types their guess, submitted via a form (both Enter key and a Submit button must work), compared case-insensitively and with whitespace trimmed against the current target word.
- Correct guesses must show clear visual success feedback (e.g. a color change) and reveal a "Next word" control; incorrect guesses must show a distinct, re-triggerable visual error state (e.g. a shake animation that restarts even on consecutive wrong guesses).
- A "Hint" button that reveals exactly one additional letter of the target word in its correct position each time it's clicked (not the same letter twice), displayed separately from the scrambled letters so the puzzle itself isn't spoiled, and tracks how many hints were used this round.
- A scoring system where each correct answer awards points that decrease as more hints are used for that round, with a sensible minimum floor so no correct answer is ever worth zero, plus a running total score and round counter displayed live.
- A "Shuffle" button that re-randomizes only the displayed order of the scrambled letters for the current word, without changing the answer, resetting hints, or affecting score.
- A "New game" control that resets score, round count, and hint tracking, and starts a fresh pass through a reshuffled word pool.
- Accessible feedback: use aria-live for the correct/incorrect status message so screen reader users get the result announced automatically.

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

  1. 1
    Unscramble and submit a guessRead the scrambled letter tiles at the top of the card, type the word you think they spell into the input, and press Enter or click Submit. handleSubmit() compares your trimmed, lowercased guess against currentWord and shows a green success state or a red shake animation.
  2. 2
    Use Shuffle for a fresh lookClick "Shuffle" to re-run scrambleWord() on the same target word, producing a new random letter order with a pop-in tile animation. This is purely visual — it never changes the answer or affects your score, it just gives you a different arrangement to look at if you feel stuck.
  3. 3
    Reveal letters with HintClick "Hint" to reveal one additional random letter in its correct position within the answer-key boxes beneath the tiles, tracked in the hintedPositions Set. Each hint reduces the points you can earn for that round from a maximum of 100 down to a floor of 25, so use them sparingly for a higher score.
  4. 4
    Advance rounds and track your scoreAfter a correct guess, click "Next word →" to call pickWord() again, which pulls the next word from the shuffled pool, resets hints, and generates a new scramble. The Score, Round, and Hints used counters in the stat row update live via updateStats().
  5. 5
    Start a clean run with New gameClick "New game" in the header to reset score to 0, round to 1, and reshuffle the entire 15-word pool via refillPool() before picking the first word — useful for demoing the game from a fresh state or starting a new competitive round.
  6. 6
    Customise the word list and difficultyEdit the WORDS array in the JS panel to add your own theme (countries, animals, tech terms). Adjust the scoring formula in handleSubmit() — Math.max(100 - hintsUsed * 25, 25) — to change how steeply hints reduce points, or add difficulty tiers by filtering WORDS by length.

Real-world uses

Common Use Cases

Vocabulary and spelling practice for language learning apps
Unscramble games are a proven format for reinforcing spelling and vocabulary recall because the player must actively reconstruct the word rather than passively recognise it. Swap the WORDS array for vocabulary from a specific language course or spelling curriculum, and the hint system doubles as a built-in scaffolding mechanism for learners who need partial support without giving away the full answer.
Daily mini-game or engagement feature on a content site
Embed this as a lightweight daily puzzle on a blog, newsletter landing page, or community site to increase time-on-page and repeat visits. Because the word pool reshuffles endlessly via refillPool(), the same component works as a bottomless casual game or, with a date-seeded random function, a shareable "word of the day" challenge similar to Wordle-style daily puzzles.
Onboarding or loading-screen filler for productivity tools
Drop this into an app loading state, empty dashboard, or "nothing to show yet" screen to give users something engaging to interact with instead of a static spinner. The self-contained scoring and round system means it needs no backend or account system to feel like a real, complete mini-game.
Teaching UI feedback states: success, error, and progressive reveal
The correct/shake/hint states make this a compact reference for building clear, immediate input feedback — a pattern also useful in form validation, quiz interfaces, and the Quick Math Arithmetic Game. Swap the accent colour #6366f1 and tile styling to match your design system while keeping the underlying interaction logic intact.
Learn the Fisher-Yates shuffle and Set-based state tracking
This snippet is a clean, real-world example of implementing an unbiased shuffle algorithm from scratch instead of relying on a library, plus using a native JavaScript Set to track sparse, order-independent state (which letter positions have been hinted) rather than a boolean array. Both patterns generalise well beyond games — the shuffle applies to any randomised list, and Set-based tracking applies to any "which items are unlocked/visited/selected" UI state.
Portfolio or interview demo of vanilla JS game-state management
The snippet manages several interdependent pieces of state — current word, scrambled order, hinted positions, score, round, and solved flag — using nothing but plain variables and DOM APIs, no framework or state library. It is a compact, readable demonstration of manual state management discipline, useful as a code sample when discussing vanilla JS architecture in interviews or portfolio reviews.

Got questions?

Frequently Asked Questions

Fisher-Yates produces a genuinely uniform random permutation, so for short words there is a real (if reduced) chance that only two or three letters swap position, making the scramble look deceptively close to the answer. The scrambleWord() function retries up to ten times only when the shuffle produces an exact match to the original word — near-misses are intentionally left alone, since eliminating them would bias the randomness and make the puzzle less fair.

Yes — in giveHint(), the unrevealed positions are currently chosen with Math.floor(Math.random() * unrevealed.length). Replace that line with unrevealed[0] to always reveal the leftmost unrevealed letter first, which some players find more intuitive since it lets them build the word left-to-right rather than getting scattered hints.

Add a difficulty selector that filters the WORDS array by string length before calling refillPool() — for example const easyWords = WORDS.filter(w => w.length <= 5) for an easy mode and a longer-word filter for hard mode. You could also maintain three separate arrays (easy/medium/hard) and swap which one populates the pool when the difficulty control changes.

No, this snippet keeps score, round, and the word pool entirely in memory via plain JavaScript variables, so a page reload resets everything. To persist a best score across sessions, add a localStorage.getItem/setItem call similar to the pattern used in the Quick Math Arithmetic Game snippet, storing the highest score achieved in a single New game run.

Yes — add a countdown timer using setInterval alongside the existing round logic, similar to the timer pattern in the Quick Math Arithmetic Game snippet, and call a game-over routine when it reaches zero instead of waiting indefinitely for the next correct guess. You would disable the guess form and hint/shuffle buttons on timeout and show a final score summary.