
In this tutorial, I will teach you how to make a Memory Cards Game with Timer using HTML5, CSS3, and JavaScript. The complete source code of this JavaScript Memory Cards Game with a Time Limit functionality is given below.
You can download the full source code (including images) of this JavaScript Memory Cards Game with Timer at the end of this article.
<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">  
    <title>Memory Cards Game with Timer</title>
    <link rel="stylesheet" href="style.css">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
  </head>
  <body>
    <div class="wrapper">
      <ul class="cards">
        <li class="card">
          <div class="view front-view">
            <img src="images/que_icon.svg" alt="icon">
          </div>
          <div class="view back-view">
            <img src="images/img-1.png" alt="card-img">
          </div>
        </li>
        <li class="card">
          <div class="view front-view">
            <img src="images/que_icon.svg" alt="icon">
          </div>
          <div class="view back-view">
            <img src="images/img-2.png" alt="card-img">
          </div>
        </li>
        <li class="card">
          <div class="view front-view">
            <img src="images/que_icon.svg" alt="icon">
          </div>
          <div class="view back-view">
            <img src="images/img-3.png" alt="card-img">
          </div>
        </li>
        <li class="card">
          <div class="view front-view">
            <img src="images/que_icon.svg" alt="icon">
          </div>
          <div class="view back-view">
            <img src="images/img-4.png" alt="card-img">
          </div>
        </li>
        <li class="card">
          <div class="view front-view">
            <img src="images/que_icon.svg" alt="icon">
          </div>
          <div class="view back-view">
            <img src="images/img-5.png" alt="card-img">
          </div>
        </li>
        <li class="card">
          <div class="view front-view">
            <img src="images/que_icon.svg" alt="icon">
          </div>
          <div class="view back-view">
            <img src="images/img-6.png" alt="card-img">
          </div>
        </li>
        <li class="card">
          <div class="view front-view">
            <img src="images/que_icon.svg" alt="icon">
          </div>
          <div class="view back-view">
            <img src="images/img-5.png" alt="card-img">
          </div>
        </li>
        <li class="card">
          <div class="view front-view">
            <img src="images/que_icon.svg" alt="icon">
          </div>
          <div class="view back-view">
            <img src="images/img-6.png" alt="card-img">
          </div>
        </li>
        <li class="card">
          <div class="view front-view">
            <img src="images/que_icon.svg" alt="icon">
          </div>
          <div class="view back-view">
            <img src="images/img-1.png" alt="card-img">
          </div>
        </li>
        <li class="card">
          <div class="view front-view">
            <img src="images/que_icon.svg" alt="icon">
          </div>
          <div class="view back-view">
            <img src="images/img-2.png" alt="card-img">
          </div>
        </li>
        <li class="card">
          <div class="view front-view">
            <img src="images/que_icon.svg" alt="icon">
          </div>
          <div class="view back-view">
            <img src="images/img-3.png" alt="card-img">
          </div>
        </li>
        <li class="card">
          <div class="view front-view">
            <img src="images/que_icon.svg" alt="icon">
          </div>
          <div class="view back-view">
            <img src="images/img-4.png" alt="card-img">
          </div>
        </li>
        <div class="details">
          <p class="time">Time: <span><b>20</b>s</span></p>
          <p class="flips">Flips: <span><b>0</b></span></p>
          <button>Refresh</button>
        </div>
      </ul>
    </div>
    <script src="script.js"></script>
    
  </body>
</html>const cards = document.querySelectorAll(".card"),
timeTag = document.querySelector(".time b"),
flipsTag = document.querySelector(".flips b"),
refreshBtn = document.querySelector(".details button");
let maxTime = 20;
let timeLeft = maxTime;
let flips = 0;
let matchedCard = 0;
let disableDeck = false;
let isPlaying = false;
let cardOne, cardTwo, timer;
function initTimer() {
    if(timeLeft <= 0) {
        return clearInterval(timer);
    }
    timeLeft--;
    timeTag.innerText = timeLeft;
}
function flipCard({target: clickedCard}) {
    if(!isPlaying) {
        isPlaying = true;
        timer = setInterval(initTimer, 1000);
    }
    if(clickedCard !== cardOne && !disableDeck && timeLeft > 0) {
        flips++;
        flipsTag.innerText = flips;
        clickedCard.classList.add("flip");
        if(!cardOne) {
            return cardOne = clickedCard;
        }
        cardTwo = clickedCard;
        disableDeck = true;
        let cardOneImg = cardOne.querySelector(".back-view img").src,
        cardTwoImg = cardTwo.querySelector(".back-view img").src;
        matchCards(cardOneImg, cardTwoImg);
    }
}
function matchCards(img1, img2) {
    if(img1 === img2) {
        matchedCard++;
        if(matchedCard == 6 && timeLeft > 0) {
            return clearInterval(timer);
        }
        cardOne.removeEventListener("click", flipCard);
        cardTwo.removeEventListener("click", flipCard);
        cardOne = cardTwo = "";
        return disableDeck = false;
    }
    setTimeout(() => {
        cardOne.classList.add("shake");
        cardTwo.classList.add("shake");
    }, 400);
    setTimeout(() => {
        cardOne.classList.remove("shake", "flip");
        cardTwo.classList.remove("shake", "flip");
        cardOne = cardTwo = "";
        disableDeck = false;
    }, 1200);
}
function shuffleCard() {
    timeLeft = maxTime;
    flips = matchedCard = 0;
    cardOne = cardTwo = "";
    clearInterval(timer);
    timeTag.innerText = timeLeft;
    flipsTag.innerText = flips;
    disableDeck = isPlaying = false;
    let arr = [1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6];
    arr.sort(() => Math.random() > 0.5 ? 1 : -1);
    cards.forEach((card, index) => {
        card.classList.remove("flip");
        let imgTag = card.querySelector(".back-view img");
        setTimeout(() => {
            imgTag.src = `images/img-${arr[index]}.png`;
        }, 500);
        card.addEventListener("click", flipCard);
    });
}
shuffleCard();
refreshBtn.addEventListener("click", shuffleCard);
cards.forEach(card => {
    card.addEventListener("click", flipCard);
});/* Import Google Font - Poppins */@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap');
*{
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: 'Poppins', sans-serif;
}
p{
  font-size: 20px;
}
body{
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  background: #6563ff;
}
::selection{
  color: #fff;
  background: #6563ff;
}
.wrapper{
  padding: 25px;
  background: #f8f8f8;
  border-radius: 10px;
  box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}
.cards, .card, .view, .details, p{
  display: flex;
  align-items: center;
  justify-content: center;
}
.cards{
  height: 350px;
  width: 350px;
  flex-wrap: wrap;
  justify-content: space-between;
}
.cards .card{
  cursor: pointer;
  position: relative;
  perspective: 1000px;
  transform-style: preserve-3d;
  height: calc(100% / 4 - 10px);
  width: calc(100% / 4 - 10px);
}
.card.shake{
  animation: shake 0.35s ease-in-out;
}
@keyframes shake {
  0%, 100%{
    transform: translateX(0);
  }
  20%{
    transform: translateX(-13px);
  }
  40%{
    transform: translateX(13px);
  }
  60%{
    transform: translateX(-8px);
  }
  80%{
    transform: translateX(8px);
  }
}
.cards .card .view{
  width: 100%;
  height: 100%;
  user-select: none;
  pointer-events: none;
  position: absolute;
  background: #fff;
  border-radius: 7px;
  backface-visibility: hidden;
  transition: transform 0.25s linear;
  box-shadow: 0 3px 10px rgba(0,0,0,0.1);
}
.card .front-view img{
  max-width: 17px;
}
.card .back-view{
  transform: rotateY(-180deg);
}
.card .back-view img{
  max-width: 40px;
}
.card.flip .front-view{
  transform: rotateY(180deg);
}
.card.flip .back-view{
  transform: rotateY(0);
}
.details{
  width: 100%;
  margin-top: 15px;
  padding: 0 20px;
  border-radius: 7px;
  background: #fff;
  height: calc(100% / 4 - 30px);
  justify-content: space-between;
  box-shadow: 0 3px 10px rgba(0,0,0,0.1);
}
.details p{
  font-size: 18px;
  height: 17px;
  padding-right: 18px;
  border-right: 1px solid #ccc;
}
.details p span{
  margin-left: 8px;
}
.details p b{
  font-weight: 500;
}
.details button{
  cursor: pointer;
  font-size: 14px;
  color: #6563ff;
  border-radius: 4px;
  padding: 4px 11px;
  background: #fff;
  border: 2px solid #6563ff;
  transition: 0.3s ease;
}
.details button:hover{
  color: #fff;
  background: #6563ff;
}
@media screen and (max-width: 700px) {
  .cards{
    height: 350px;
    width: 350px;
  }
  .card .front-view img{
    max-width: 16px;
  }
  .card .back-view img{
    max-width: 40px;
  }
}
@media screen and (max-width: 530px) {
  .cards{
    height: 300px;
    width: 300px;
  }
  .card .back-view img{
    max-width: 35px;
  }
  .details{
    margin-top: 10px;
    padding: 0 15px;
    height: calc(100% / 4 - 20px);
  }
  .details p{
    height: 15px;
    font-size: 17px;
    padding-right: 13px;
  }
  .details button{
    font-size: 13px;
    padding: 5px 10px;
    border: none;
    color: #fff;
    background: #6563ff;
  }
}Google Chrome has dominated web browsing for over a decade with 71.77% global market share.…
Perplexity just made its AI-powered browser, Comet, completely free for everyone on October 2, 2025.…
You've probably heard about ChatGPT Atlas, OpenAI's new AI-powered browser that launched on October 21,…
Perplexity Comet became free for everyone on October 2, 2025, bringing research-focused AI browsing to…
ChatGPT Atlas launched on October 21, 2025, but it's only available on macOS. If you're…
Two AI browsers just entered the ring in October 2025, and they're both fighting for…