You Might Also Like
Minesweeper Game — Free HTML CSS JS Snippet
Minesweeper Puzzle Game · Games · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Minesweeper Puzzle Game — Recursive Flood Fill, Safe First-Click Generation & Flag Logic

Minesweeper is one of the oldest and most instructive logic puzzles to implement, because a correct version is not just a grid of clickable squares — it requires a genuine flood-fill algorithm, deferred board generation, and careful state tracking for flags, reveals, and win detection. This snippet builds a fully playable 9x9 grid with 10 mines using nothing but vanilla JavaScript and CSS Grid, and every rule that makes Minesweeper feel fair and satisfying is implemented rather than faked.
Safe first click, generated after the fact
The classic frustration in a badly made Minesweeper clone is losing on your very first click through no fault of your own. This snippet avoids that entirely: the board starts completely empty of mines, and placeMines(excludeR, excludeC) only runs the moment the player clicks their first cell, deliberately skipping that cell and its eight neighbours when scattering the 10 mines with Math.random(). This guarantees the opening click always lands on a safe, typically zero-adjacency area, which is the standard fairness convention every reference implementation of Minesweeper follows.
Iterative flood fill, not a single-cell reveal
Clicking a cell with zero adjacent mines should cascade outward and reveal every connected empty region along with its numbered border — this is the heart of what makes Minesweeper fun to play quickly. The floodReveal() function implements this with an explicit stack rather than naive recursion (which risks call-stack depth issues on larger boards): it pushes the starting cell, pops cells one at a time, marks each revealed, and — only when that cell's adjacent count is zero — pushes all eight unrevealed neighbours back onto the stack. Cells with a positive adjacency count are still revealed and rendered with their colour-coded number, but the fill does not continue past them, exactly matching the classic Windows Minesweeper behaviour.
Colour-coded numbers and flag mode
Revealed cells with adjacent mines get a class like .n3 that maps to the traditional colour convention — 1 is blue, 2 is green, 3 is red, and so on up through 8 — purely through CSS class selectors keyed off the stored adjacent integer. Flagging is handled two ways: a right-click (contextmenu event, with preventDefault() to suppress the browser menu) toggles a flag on desktop, while a dedicated "Flag mode" toggle button lets touch users tap to flag instead of reveal, since touch devices have no reliable right-click equivalent.
Win and loss detection
Every reveal increments a revealedCount counter; the game is won the instant that count equals the total non-mine cell count (81 minus 10 mines), which is checked after every successful reveal in checkWin(). Losing triggers revealAllMines(), which reveals every mine on the board and highlights the specific one that was clicked in a brighter red so the player can see exactly what ended the round. A live mine counter subtracts placed flags from the total mine count, and a timer starts on the first click and stops the instant the game ends, both rendered in a retro seven-segment style display for authenticity.
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 floodReveal()'s stack-based traversal decides which neighbouring cells to push, and why it stops expanding past a numbered cell but not past a zero-adjacency one — understanding that boundary condition is the key to understanding the whole algorithm. It's also a great snippet to extend with AI help: ask it to add a difficulty selector that swaps ROWS/COLS/MINES and resizes the CSS grid to match, add a chording feature (clicking a revealed number that already has the correct number of adjacent flags reveals all its remaining unflagged neighbours), or persist best completion times per difficulty using localStorage the same way the 2048 snippet in this gallery persists its best score. Treat the working game as a base to question and build on, not a finished black box.
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 playable Minesweeper game in plain HTML, CSS, and JavaScript on a 9x9 grid with 10 mines — no frameworks, no libraries.
Requirements:
- Mines must not be placed until after the player's first click, and that first click (plus its immediate neighbours) must never be a mine, so the opening move is always safe.
- Clicking a cell with zero adjacent mines must cascade-reveal all connected zero-adjacency cells and their bordering numbered cells using a real flood-fill algorithm (iterative or recursive), not just the single clicked cell.
- Revealed cells with adjacent mines must show the count, colour-coded using the classic convention (1 blue, 2 green, 3 red, etc).
- Right-click must flag a hidden cell instead of revealing it, and there must be a separate flag-mode toggle so touch-only users without a right-click can flag cells too.
- Track and display a live mine counter (total mines minus flags currently placed) and a timer that starts on the first click and stops when the game ends.
- Clicking a mine must end the game immediately, reveal every mine on the board, and visually distinguish the specific mine that was clicked from the rest.
- Detect the win condition (every non-mine cell revealed) and show a distinct win state, and provide a button to start a completely fresh game with a newly randomized mine layout at any time.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
- 1Reveal your first cellClick any cell on the 9x9 grid. Mines are generated only after this first click, and the clicked cell plus its neighbours are guaranteed mine-free, so your opening move is always safe.
- 2Read the flood-fill revealIf the clicked cell has zero adjacent mines, floodReveal() cascades outward and automatically reveals the whole connected empty region plus its numbered border cells, exactly like the classic game.
- 3Flag suspected minesRight-click a hidden cell to place a flag on desktop. On touch devices, tap the "Flag mode" toggle button first, then tap cells to flag them instead of revealing them.
- 4Watch the counter and timerThe left digital counter shows mines remaining (10 minus flags placed) and the right counter is a timer that starts on your first click and freezes the instant the round ends.
- 5Win or lose the roundClicking a mine ends the game immediately and reveals every mine, with the fatal one highlighted in bright red. Revealing all 71 safe cells triggers the win state and auto-flags the remaining mines.
- 6Start a new gameClick the face button (🙂) at any time — it shows 😎 on a win or 💀 on a loss — to instantly regenerate a fresh empty board and reset the counter, timer, and flag mode.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Mines are not placed when the board is first built — the grid starts completely empty. placeMines(r, c) only runs inside handleCellClick() the moment you make your first click, and it explicitly skips the clicked cell plus its eight surrounding neighbours when scattering the 10 mines. This "safe first click" rule is the standard fairness convention in every well-made Minesweeper implementation, including Microsoft's original.
floodReveal() only ever pushes unrevealed, unflagged neighbour cells onto its stack, and it stops expanding outward from any cell whose adjacent mine count is greater than zero — it still reveals that numbered cell but does not continue past it. Since mines are never popped from the stack (the function is only ever called starting from a confirmed non-mine cell), a correct board layout guarantees the cascade can never touch a mine cell.
Yes. The ROWS, COLS, and MINES constants at the top of the JS control the entire board. Increasing MINES relative to the grid area raises difficulty; the CSS grid-template-columns/rows on .ms-board must be updated to match COLS and ROWS if you change them, since the layout is not currently computed dynamically from those constants.
Each .ms-cell is rendered as a real <button>, so it is already focusable and clickable via Enter/Space by default. For fuller accessibility, add arrow-key navigation between grid cells using a roving tabindex pattern, and add aria-label attributes reflecting each cell's state (hidden, flagged, revealed-with-count, or mine) so screen reader users get equivalent information to the visual number colours and flag icon.
A naive recursive flood fill calls itself once per revealed cell, and on a large, mostly-empty board that can produce thousands of nested calls, risking a "Maximum call stack size exceeded" error in some browsers. The iterative version in this snippet uses a plain JavaScript array as an explicit stack, achieving the identical reveal pattern without any risk of stack overflow, which matters more as you scale the grid up from the default 9x9 to Expert-sized 30x16 boards.