๐ŸŽฎ Get a game ยท $19

๐Ÿƒ Free game 6 of 20 ยท copy & paste

Memory Card Game in HTML, CSS & JavaScript: Free Code

Flip two cards at a time and find all the pairs in as few moves as you can. The cards flip with a CSS 3D animation and use emoji, so there are no images. Copy the code below, save it as memory-game.html and open it in any browser.

Type
Puzzle
Level
Easy
Code
111 lines, 1 file
Libraries
None
Works on
Phones & computers
You learn
Fisherโ€“Yates shuffle and 3D card flips in CSS

โ–ฒ Play it right here. Click or tap a card to flip it.

What's in this free Memory Card Game game

The code (111 lines, one file)

memory-game.htmlโฌ‡ Download
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Memory Card Game</title>
<!--
  Free HTML Memory (card matching) game โ€” one file, no libraries. Save it as memory-game.html and open it in a browser.
  From htmlgamescode.com (free to use in your own projects).
  Flip two cards at a time and find all the pairs in as few moves as you can.
-->
<style>
  * { box-sizing: border-box; margin: 0; padding: 0; }
  body {
    min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px;
    background: #1b1446; color: #fff; font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
    padding: 12px; user-select: none; -webkit-user-select: none;
  }
  .bar { width: min(92vw, 400px); display: flex; justify-content: space-between; gap: 6px; font-weight: 700; }
  .bar span { background: #2a2170; padding: 6px 10px; border-radius: 10px; }
  .grid { display: grid; gap: 10px; width: min(92vw, 400px, calc(100vh - 130px)); }
  .card { position: relative; aspect-ratio: 1; border: 0; background: none; cursor: pointer; transform-style: preserve-3d; transition: transform .35s; }
  .card.open { transform: rotateY(180deg); }
  .card span {
    position: absolute; inset: 0; display: grid; place-items: center; border-radius: 16px; backface-visibility: hidden;
    -webkit-backface-visibility: hidden; font-size: min(10vw, 42px, calc((100vh - 130px) / 9)); box-shadow: 0 5px 0 rgba(0,0,0,.3);
  }
  .card .back { background: linear-gradient(135deg, #ff5fb1, #8b5cff); color: #fff; font-weight: 900; }
  .card .front { background: #fff; transform: rotateY(180deg); }
  .card.done .front { background: #b9ffc8; }
  .msg { position: fixed; inset: 0; display: grid; place-items: center; background: rgba(10, 6, 36, .7); }
  .msg[hidden] { display: none; }
  .box { text-align: center; background: #2a2170; padding: 26px 30px; border-radius: 20px; }
  .box h1 { font-size: 28px; margin-bottom: 6px; }
  .box p { opacity: .85; margin-bottom: 16px; }
  .btn { border: 0; border-radius: 14px; padding: 12px 28px; font-size: 18px; font-weight: 700; color: #fff; background: #36d35a; box-shadow: 0 4px 0 #1d9a3a; cursor: pointer; }
</style>
</head>
<body>
  <div class="bar"><span>Moves: <b id="moves">0</b></span><span>Time: <b id="time">0</b>s</span><span>Best: <b id="best">โ€“</b></span></div>
  <div class="grid" id="grid"></div>
  <button class="btn" id="restart">Shuffle &amp; restart</button>
  <div class="msg" id="msg" hidden><div class="box"><h1>๐ŸŽ‰ You did it!</h1><p id="result"></p><button class="btn" id="again">Play again</button></div></div>

<script>
  // ---- settings: change these to make it your own ----
  const EMOJIS = ["๐ŸŽ", "๐ŸŒ", "๐Ÿ‡", "๐Ÿ“", "๐Ÿ’", "๐Ÿฅ", "๐Ÿ", "๐Ÿ‘"];   // one pair of cards per emoji (8 = 16 cards)
  const COLUMNS = 4;             // cards per row
  const FLIP_BACK_MS = 800;      // how long two wrong cards stay face up

  const $ = id => document.getElementById(id);
  const load = k => { try { return Number(localStorage.getItem(k)) || 0; } catch { return 0; } };
  const save = (k, v) => { try { localStorage.setItem(k, v); } catch {} };
  let first, lock, moves, matched, started, timer;
  let best = load("memory-best");

  function shuffle(a) {                    // Fisherโ€“Yates shuffle
    for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; }
    return a;
  }

  function deal() {
    const grid = $("grid");
    grid.innerHTML = "";
    grid.style.gridTemplateColumns = `repeat(${COLUMNS}, 1fr)`;
    shuffle([...EMOJIS, ...EMOJIS]).forEach(e => {
      const c = document.createElement("button");
      c.className = "card"; c.dataset.e = e; c.setAttribute("aria-label", "Hidden card");
      c.innerHTML = `<span class="back">?</span><span class="front">${e}</span>`;
      c.addEventListener("click", () => flip(c));
      grid.appendChild(c);
    });
    first = null; lock = false; moves = 0; matched = 0; started = 0;
    clearInterval(timer); $("msg").hidden = true; update();
  }

  function flip(c) {
    if (lock || c === first || c.classList.contains("open")) return;
    if (!started) { started = Date.now(); timer = setInterval(update, 1000); }
    c.classList.add("open");
    if (!first) { first = c; return; }
    moves++; update();
    if (first.dataset.e === c.dataset.e) {          // a pair!
      first.classList.add("done"); c.classList.add("done");
      first = null; matched++;
      if (matched === EMOJIS.length) win();
    } else {                                        // not a pair: flip both back
      lock = true;
      const a = first; first = null;
      setTimeout(() => { a.classList.remove("open"); c.classList.remove("open"); lock = false; }, FLIP_BACK_MS);
    }
  }

  function seconds() { return started ? Math.floor((Date.now() - started) / 1000) : 0; }
  function update() { $("moves").textContent = moves; $("time").textContent = seconds(); $("best").textContent = best || "โ€“"; }

  function win() {
    clearInterval(timer);
    const newBest = !best || moves < best;
    if (newBest) { best = moves; save("memory-best", best); }
    $("result").textContent = `${moves} moves in ${seconds()} seconds.${newBest ? " New best! ๐Ÿ†" : ""}`;
    update();
    setTimeout(() => { $("msg").hidden = false; }, 500);
  }

  $("restart").addEventListener("click", deal);
  $("again").addEventListener("click", deal);
  deal();
</script>
</body>
</html>

How to use it

  1. Click Copy code (or Download).
  2. Paste it into any text editor and save it as memory-game.html.
  3. Open the file in Chrome, Safari, Firefox or Edge. That's it, it runs offline.

How the code works

Shuffling

The emoji list is doubled and shuffled with the Fisherโ€“Yates shuffle, which gives every order the same chance.

Flipping

Each card has a front and a back face with backface-visibility hidden. Adding the open class rotates the card 180ยฐ.

Checking a pair

The first card is remembered. When the second one opens, matching cards stay open; otherwise both flip back after FLIP_BACK_MS.

Make it your own

All the settings are at the top of the script. Change a value, save and reload:

SettingDefaultWhat it does
EMOJISa listone pair of cards per emoji (8 = 16 cards)
COLUMNS4cards per row
FLIP_BACK_MS800how long two wrong cards stay face up

Put it on your website

Upload memory-game.html next to your page and add this where the game should appear:

<iframe src="memory-game.html" width="460" height="620" style="border:0;max-width:100%" title="Memory Card Game game"></iframe>

FAQ

Is this Memory Card Game code really free?

Yes. Copy it and use it in your own website, school project or app, change it as much as you like and publish it, even commercially. A link back to HTML Games Code is welcome but not required.

How do I run the Memory Card Game game?

Copy the code, paste it into a text editor, save the file as memory-game.html and double-click it. It opens in your browser and works offline; there is nothing to install.

What are the controls?

Click or tap a card to flip it.

Does it work on phones?

Yes. It has touch controls and fits a phone screen, and it works with a keyboard and mouse on a computer.

Can I put it on my website?

Yes. Upload memory-game.html and link to it, or show it inside an iframe on any page (see "Put it on your website" above).

Can I turn it into an Android app?

Yes. Any HTML game can be packaged as an Android APK. Our HTML Games APK Builder does it for our premium games, or you can use a tool like htmltoapk.com for this file.

More free HTML games