Next.jstailwindguide

How To Create A Card Matching Game Using React

By Everett Quebral
Picture of the author
Published on
image alt attribute

Building a Card Matching Game in React: A Step-by-Step Guide

If you're looking for a fun way to sharpen your React skills, building a memory-based Card Matching Game is a great project. It combines core frontend techniques—state management, component design, effects, and conditional rendering—into a single interactive application.

In this guide, we’ll walk through:

  • Designing the structure of the matching game
  • Managing state with React hooks (useState, useEffect)
  • Handling game logic (flipping, matching, resetting)
  • Styling the board and cards with CSS
  • Ideas for extending the game (scores, timers, difficulty levels)

What We're Building

The core idea: display a grid of cards, all face-down. The user clicks to flip two cards. If they match, they stay visible. If not, they flip back after a short delay. The game ends when all matches are found.

Here’s what we’ll need:

  • A deck of card objects (duplicated and shuffled)
  • A Card component to display each card
  • A main GameBoard to manage state
  • Logic for handling flips, checking matches, and resetting

Step 1: Set Up the Project

Create a new React app:

npx create-react-app card-matching-game --template typescript
cd card-matching-game

Add Tailwind CSS (optional for styling):

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Update your tailwind.config.js and include the Tailwind directives in index.css.


Step 2: Create Card Data

Create a file src/data/cards.ts:

const cardImages = [
  { src: '/img/helmet.png', matched: false },
  { src: '/img/potion.png', matched: false },
  { src: '/img/ring.png', matched: false },
  { src: '/img/scroll.png', matched: false },
  { src: '/img/shield.png', matched: false },
  { src: '/img/sword.png', matched: false }
]

export default cardImages

🔁 Each image will appear twice in the game deck (for matching).


Step 3: Build the Card Component

Create src/components/Card.tsx:

interface CardProps {
  card: {
    src: string
    matched: boolean
    id: number
  }
  handleChoice: (card: any) => void
  flipped: boolean
  disabled: boolean
}

export default function Card({ card, handleChoice, flipped, disabled }: CardProps) {
  const handleClick = () => {
    if (!disabled) handleChoice(card)
  }

  return (
    <div className="w-24 h-32 cursor-pointer perspective" onClick={handleClick}>
      <div className={`relative w-full h-full transition-transform duration-500 ${flipped ? 'rotate-y-180' : ''}`}>
        <img
          className="absolute w-full h-full backface-hidden"
          src={card.src}
          alt="card front"
        />
        <div className="absolute w-full h-full bg-gray-800 backface-hidden rotate-y-180" />
      </div>
    </div>
  )
}

✨ Styling classes assume Tailwind CSS, but you can adapt to regular CSS or styled-components.


Step 4: Game Logic in App.tsx

import { useEffect, useState } from 'react'
import cardImages from './data/cards'
import Card from './components/Card'

function App() {
  const [cards, setCards] = useState<any[]>([])
  const [firstChoice, setFirstChoice] = useState<any>(null)
  const [secondChoice, setSecondChoice] = useState<any>(null)
  const [disabled, setDisabled] = useState(false)
  const [turns, setTurns] = useState(0)

  const shuffleCards = () => {
    const shuffled = [...cardImages, ...cardImages]
      .map((card) => ({ ...card, id: Math.random() }))
      .sort(() => Math.random() - 0.5)
    setFirstChoice(null)
    setSecondChoice(null)
    setCards(shuffled)
    setTurns(0)
  }

  const handleChoice = (card: any) => {
    firstChoice ? setSecondChoice(card) : setFirstChoice(card)
  }

  useEffect(() => {
    if (firstChoice && secondChoice) {
      setDisabled(true)
      if (firstChoice.src === secondChoice.src) {
        setCards((prev) =>
          prev.map((card) =>
            card.src === firstChoice.src ? { ...card, matched: true } : card
          )
        )
        resetTurn()
      } else {
        setTimeout(() => resetTurn(), 1000)
      }
    }
  }, [firstChoice, secondChoice])

  const resetTurn = () => {
    setFirstChoice(null)
    setSecondChoice(null)
    setTurns((t) => t + 1)
    setDisabled(false)
  }

  useEffect(() => {
    shuffleCards()
  }, [])

  return (
    <div className="text-center">
      <h1 className="text-2xl font-bold mb-4">Card Matching Game</h1>
      <button
        onClick={shuffleCards}
        className="mb-4 px-4 py-2 bg-indigo-600 text-white rounded"
      >
        New Game
      </button>
      <div className="grid grid-cols-4 gap-4 justify-center">
        {cards.map((card) => (
          <Card
            key={card.id}
            card={card}
            handleChoice={handleChoice}
            flipped={card === firstChoice || card === secondChoice || card.matched}
            disabled={disabled}
          />
        ))}
      </div>
      <p className="mt-4">Turns: {turns}</p>
    </div>
  )
}

export default App

🧠 Logic Highlights:

  • We track two selected cards using state
  • If they match, matched is set to true
  • If not, they flip back after 1 second
  • A "New Game" button reshuffles and resets the board

Styling Tips

To enhance the visual appeal:

  • Add card flip animations using transform: rotateY(180deg)
  • Show a success animation when all pairs are matched
  • Add sound effects on flip and match
  • Display emojis or text as fallback if images are not used

Advanced Extensions

Want to make it more interactive? Try adding:

  • Timer and best-time tracker
  • 🧠 Difficulty levels (3x4, 4x4, 6x6)
  • 🏆 Leaderboard using localStorage or a backend
  • 👨‍👩‍👧‍👦 Multiplayer support with WebSockets or Firebase
  • 🔄 Animations with Framer Motion or GSAP

Final Thoughts

This game is a great introduction to:

  • Handling stateful components in React
  • Using effects to trigger logic
  • Structuring reusable components like cards
  • Managing game state with multiple variables

You can easily take this foundation and customize it into your own theme—Pokemon, icons, countries, emojis, or even math symbols for educational use.


Resources

Stay Tuned

Want to become a Next.js pro?
The best articles, links and news related to web development delivered once a week to your inbox.