Mini Sudoku Puzzle (6x6) — Free HTML CSS JS Snippet

Mini Sudoku Puzzle (6x6) · Games · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Randomised recursive backtracking generator (fillGrid) produces a different valid solved grid on every call
Single isValidPlacement() function enforces row, column, and 2x3 box uniqueness, reused for both generation and live play
Live constraint checking on every entry via findConflicts(), highlighting duplicates immediately rather than on submit
Dual input paths (on-screen number pad and keyboard 1-6) both routed through one shared enterNumber() function
Clue cells rendered read-only and visually distinct from editable blanked cells
Win detection requires both a fully filled grid and zero constraint conflicts simultaneously
Live elapsed-time timer using Date.now() deltas, frozen at the moment of a correct solve
Fisher-Yates shuffle used both for candidate digit order during generation and for clue-removal cell selection

About this UI Snippet

Mini Sudoku Puzzle (6x6) — Backtracking Generator, Live Constraint Checking & Number Pad Input

Screenshot of the Mini Sudoku Puzzle (6x6) snippet rendered live

Full 9x9 Sudoku is too large a puzzle to comfortably fit and finish inside a UI snippet demo, but the underlying constraint-satisfaction rules — every digit unique within its row, column, and box — are exactly the same on a smaller board. This snippet implements a genuinely playable 6x6 variant using digits 1 through 6 arranged in six 2x3 boxes, built with a real randomised backtracking generator and live constraint validation on every keystroke, teaching the identical algorithmic technique as the full-size game at a scope that is actually completable in a couple of minutes.

Generating a valid solved grid with randomised backtracking

generateSolution() starts from an empty 6x6 grid and calls fillGrid(), a recursive backtracking function that fills cells one at a time in row-major order. At each empty position, it shuffles the candidate digits 1 through 6 with a Fisher-Yates shuffle() (rather than trying them in fixed numeric order, which would always generate the same handful of grids) and attempts each candidate via isValidPlacement(). If a candidate is valid, it is placed and the function recurses into the next position; if the recursion eventually fails to complete the grid, that candidate is undone (reset to 0) and the next shuffled candidate is tried. This is the standard constraint-satisfaction backtracking pattern — try, recurse, undo on failure — and because the candidate order is randomised at every cell, repeated calls produce different valid solved grids rather than the same one every time.

Row, column, and 2x3 box validation

isValidPlacement(grid, r, c, val) is the single function responsible for enforcing every Sudoku rule, and it is reused both during generation and during live gameplay. It checks the target value does not already appear elsewhere in row r or column c, then computes the top-left corner of the containing 2x3 box with Math.floor(r / BOX_H) * BOX_H and Math.floor(c / BOX_W) * BOX_W (where BOX_H is 2 and BOX_W is 3, since a 6x6 grid divides into six boxes each spanning 3 columns and 2 rows) and scans every cell inside that box for a duplicate. Because the box dimensions are named constants rather than hard-coded literals, the same function would work unmodified on a differently-shaped mini-Sudoku variant if the constants were changed.

Puzzle creation and live conflict detection

makePuzzle() takes the completed solution and blanks out a fixed number of cells (16 of the 36, leaving 20 clues) at positions chosen by shuffling every cell index and taking the first N — ensuring the removed cells are different on every new puzzle. Cells that still hold their original solved value render as read-only .clue cells; blanked cells become editable. Crucially, validation does not wait until the grid is full: every render calls findConflicts(), which loops over every filled cell in the current userGrid and re-runs isValidPlacement() against it, adding any cell that now violates a row, column, or box constraint to a Set of conflict coordinates. Those cells immediately render with a red .conflict background, giving the player real-time feedback the instant an entry creates a duplicate, rather than only at a final "check" action.

Number pad and keyboard input working together

Selecting an editable cell highlights it and enables entry via two equivalent input paths: clicking a digit on the on-screen .pad-btn number pad, or pressing 1 through 6 on the keyboard while a cell is selected (handled by a single document-level keydown listener). Both paths call the same enterNumber() function, so validation and win-checking logic exists in exactly one place regardless of input method. A "Clear" pad button and the Backspace/Delete keys both route to enterNumber(0), blanking the selected cell.

Win detection and the timer

checkWin() first confirms every cell in the grid is non-zero via isGridComplete(), then re-runs findConflicts() — the puzzle only counts as solved when the grid is completely filled *and* zero conflicts exist simultaneously, which correctly rejects a full-but-invalid grid. A live timer starts the moment a new puzzle is generated, using Date.now() deltas updated on a 500ms interval, and stops the instant the win condition is met, with the final elapsed time shown in the win message via the same formatTime() helper used for the live display.

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 fillGrid()'s backtracking recursion works, including what happens at the moment a dead-end forces it to undo a placement and try the next shuffled candidate — walking through that trace is one of the clearest ways to actually understand backtracking algorithms. You could also ask it to add a solution-uniqueness check after clue removal so every generated puzzle is guaranteed to have exactly one valid solution rather than potentially several, implement a difficulty selector that adjusts the cellsToRemove count, or add a "hint" button that reveals one correct cell using the stored solution array without giving away the whole puzzle. Each is a natural next step once the core generation and validation logic is already understood.

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 playable 6x6 mini Sudoku puzzle in plain HTML, CSS, and JavaScript with real constraint-satisfaction logic — no frameworks, no libraries.

Requirements:
- A 6x6 grid divided into six 2x3 boxes, using digits 1 through 6, generated by first producing a fully valid solved grid via randomised recursive backtracking (shuffle candidate order at each cell, place if valid, recurse, undo and try the next candidate on failure).
- A single reusable validation function that checks row, column, and 2x3 box uniqueness for a given cell and value, used both during generation and during live gameplay.
- After generating the solution, remove a subset of cells (leaving a reasonable number of clues) to form the playable puzzle; clue cells must render as fixed/read-only while removed cells become editable inputs.
- Live validation on every entry: if a newly entered digit creates a duplicate within its row, column, or box, immediately highlight the conflicting cells (not just the new one) — re-check the whole grid on every input since one entry can retroactively invalidate a different existing cell.
- Support both an on-screen number pad UI for entering 1-6 and native keyboard 1-6 input when a cell is focused/selected, both routed through the same entry-handling logic so there is no duplicated validation code.
- Detect the win state only when the grid is both completely filled and has zero constraint conflicts simultaneously — a full but invalid grid must not count as solved.
- Track and display elapsed time from puzzle start to solve, and provide a "New Puzzle" action that regenerates a fresh solution and clue layout.

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
    Select an editable cellClick any blank cell (clue cells shown with a shaded background are fixed and cannot be selected). The selected cell highlights so you know which one your next number entry will fill.
  2. 2
    Enter a number via the pad or keyboardClick a digit 1-6 on the on-screen number pad, or simply press the corresponding number key on your keyboard while a cell is selected — both call the same enterNumber() function and update the grid identically.
  3. 3
    Watch for live conflict highlightingEvery entry immediately re-runs findConflicts() across the whole grid. If your new digit duplicates another value in the same row, column, or 2x3 box, both the new cell and the conflicting cell turn red via the .conflict class right away — no separate "check" step needed.
  4. 4
    Clear a mistakeClick the "Clear" button on the number pad, or press Backspace/Delete on your keyboard, to blank the currently selected editable cell back to empty and clear any conflict highlighting tied to it.
  5. 5
    Reach the solved statecheckWin() confirms you have won only when every cell is filled and findConflicts() returns zero conflicts simultaneously — a completely full but invalid grid does not trigger the "Solved!" message.
  6. 6
    Track your time and start a new puzzleA live timer runs from the moment a puzzle loads and freezes the instant you solve it, shown in the win banner. Click "New Puzzle" to generate a freshly randomised solution and clue layout via generateSolution() and makePuzzle().

Real-world uses

Common Use Cases

Teaching backtracking algorithms with a genuinely solvable demo
Full 9x9 Sudoku backtracking can take a moment to visualise and complete, making it a poor fit for a live teaching demo. This 6x6 scope keeps generateSolution() fast and the resulting puzzle actually finishable within a short classroom or tutorial session, while using the exact same recursive try-recurse-undo algorithm as the full-size version.
Daily mini-puzzle feature for a web app or newsletter
Because a 6x6 Sudoku is small enough to solve in a couple of minutes, it fits naturally as a daily engagement feature — a "puzzle of the day" widget in a dashboard sidebar or embedded in an email digest, similar in spirit to daily word and logic puzzle formats that drive repeat visits.
Constraint-satisfaction reference for other rule-based grid puzzles
The pattern of a single reusable validation function checked live on every input, rather than only at submission, generalises to any rule-based grid interface — form grids with cross-field uniqueness rules, seating charts, or scheduling grids can borrow the same live-conflict-highlighting approach demonstrated here.
Accessible dual-input pattern for number entry
Supporting both a tappable on-screen number pad and direct keyboard input for the same action is good practice for any numeric-entry UI intended to work well on both touch devices and desktop — this snippet is a compact reference for wiring both input paths through one shared handler without duplicating validation logic.
Compact demonstration of Fisher-Yates shuffle in two different roles
The same shuffle() helper is reused for two distinct purposes: randomising candidate digit order during backtracking generation, and randomising which cell positions get blanked out to form the puzzle. Studying both call sites is a good way to see how one small, correct utility function can serve multiple randomisation needs in a single program.
Logic-puzzle break in an onboarding, loading, or waiting-room flow
A 6x6 Sudoku resolves quickly enough to work as a short, self-contained distraction during an otherwise idle wait, without the time commitment a full 9x9 puzzle would demand — pairing well with faster-paced reaction games like the Click Speed Test on a waiting-room games menu.

Got questions?

Frequently Asked Questions

A 9x9 grid needs 81 cells filled and typically 25-30+ clues to remain uniquely solvable, which is too large a time commitment for a quick UI demo. A 6x6 grid uses digits 1 through 6 in six 2x3 boxes, keeping the same row/column/box uniqueness rule set intact while being genuinely completable in a couple of minutes — the algorithm (backtracking generation, live constraint checking) is identical, only the board dimensions differ.

fillGrid() is a recursive function that places a shuffled-order candidate digit into each cell only if isValidPlacement() confirms it does not violate row, column, or box uniqueness, then recurses into the next cell. If a later cell has no valid candidates, the function backtracks — undoing the previous cell's placement and trying its next candidate. Because every placement is validated before being kept, the completed grid is guaranteed to satisfy every Sudoku constraint by construction.

findConflicts() re-scans every filled cell on every render because entering one number can retroactively make a previously-fine cell invalid if it happens to share a row, column, or box with the new entry. Checking only the newly entered cell would miss the case where the older cell is the one now flagged as a duplicate, so a full pass keeps the highlighting always accurate.

makePuzzle() removes 16 of the 36 cells by default, leaving 20 clues. To make puzzles harder, increase the cellsToRemove constant (fewer clues generally means more difficult, though at very low clue counts a puzzle may have multiple valid solutions since this generator does not check solution uniqueness after removal — for a strict single-solution guarantee you would need to add a uniqueness-checking solver pass).

Yes, the timer runs continuously from the moment a new puzzle is generated until the exact moment checkWin() confirms a valid, complete solution, using Date.now() deltas updated twice a second. It does not pause automatically if you switch tabs or step away; if you want a pause feature, you would need to track visibilitychange events and freeze startTime accordingly.