How to Create a Hangman Game in ReactJS and Tailwind CSS
“This article was originally published at https://www.codingnepalweb.com.”
Hangman is the classic word-guessing game you’ve likely enjoyed playing. But as a beginner ReactJS developer, have you ever thought about creating your version of the Hangman game? Creating a Hangman game helps you grasp the fundamentals of ReactJS and Tailwind CSS, while also improving your problem-solving and logical thinking skills.
In this blog post, I’ll guide you through creating a fully functional Hangman game using ReactJS and Tailwind CSS. In this game, the player has to guess all the letters of a randomly generated word within a given number of tries. There is also a hangman graphic that will progressively appear on the gallows for each incorrect guess.
Why Create a Hangman Game in ReactJS?
By creating this Hangman game project using React JS and Tailwind CSS, you can gain the following skills:
- ReactJS Fundamentals: Get hands-on experience with React components, state management, and hooks.
- Tailwind CSS Skills: Learn how to create a responsive and visually appealing UI using Tailwind.
- Practical Application: Create a project you can showcase in your portfolio or use in real-life scenarios.
- Problem-Solving: Enhance your problem-solving skills by implementing game logic and managing state.
Demo of Hangman Game in ReactJS & Tailwind
Setting Up the Project
Before we start making the Hangman game with ReactJS and Tailwind CSS, make sure you have Node.js installed on your computer. If you don’t have it, you can download and install it from the official Node.js website.
Create a Project Folder:
- Make a new folder, for instance, “hangman-game”.
- Open this folder in your VS Code editor.
Initialize the Project:
Open your terminal by pressing Ctrl + J and then use Vite to create a new React app with this command:
npm create vite@latest ./ -- --template react
Install Tailwind CSS:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Replace the code in tailwind.config.js
with the provided configuration.
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
Start the Development Server:
npm run dev
If your project is up and running in your browser, congratulations! You’ve successfully set up your project. Now, let’s move on to the next step modifying folders and files.
Modify folder and CSS Files:
- Remove the default
assets
folder andApp.css
file. - Download the Images and place them directly into your public folder. Ensure you only include the images and not the images folder itself.
- Replace the content of
index.css
with the provided code.
/* Importing Google Fonts - Open Sans */
@import url("https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700&display=swap");
@tailwind base;
@tailwind components;
@tailwind utilities;
* {
font-family: "Open Sans", sans-serif;
}
body {
height: 100vh;
width: 100%;
background: url("hangman-bg.jpg");
background-size: cover;
background-position: center;
}
Creating the Components
Within the src directory of your project, organize your files by creating three different folders: “constants”, “components”, and “context”. Inside these folders, create the following files:
- constants/index.js
- components/GameBoard.jsx
- components/GameKeyboard.jsx
- components/HangmanIllustration.jsx
- components/GameOverModal.jsx
- context/GameContext.jsx
Adding the Codes
Add the respective code to each newly created file. These files define the layout and functionality of the Hangman Game.
In constants/index.js
, add the code. This file serves as a main location for defining and managing the word list used for the game.
// List of words and their corresponding hints for the game
export const wordList = [
{
word: "guitar",
hint: "A musical instrument with strings.",
},
{
word: "oxygen",
hint: "A colorless, odorless gas essential for life.",
},
{
word: "mountain",
hint: "A large natural elevation of the Earth's surface.",
},
{
word: "painting",
hint: "An art form using colors on a surface to create images or expression.",
},
{
word: "astronomy",
hint: "The scientific study of celestial objects and phenomena.",
},
{
word: "football",
hint: "A popular sport played with a spherical ball.",
},
{
word: "chocolate",
hint: "A sweet treat made from cocoa beans.",
},
{
word: "butterfly",
hint: "An insect with colorful wings and a slender body.",
},
{
word: "history",
hint: "The study of past events and human civilization.",
},
{
word: "pizza",
hint: "A savory dish consisting of a round, flattened base with toppings.",
},
{
word: "jazz",
hint: "A genre of music characterized by improvisation and syncopation.",
},
{
word: "camera",
hint: "A device used to capture and record images or videos.",
},
{
word: "diamond",
hint: "A precious gemstone known for its brilliance and hardness.",
},
{
word: "adventure",
hint: "An exciting or daring experience.",
},
{
word: "science",
hint: "The systematic study of the structure and behavior of the physical and natural world.",
},
{
word: "bicycle",
hint: "A human-powered vehicle with two wheels.",
},
{
word: "sunset",
hint: "The daily disappearance of the sun below the horizon.",
},
{
word: "coffee",
hint: "A popular caffeinated beverage made from roasted coffee beans.",
},
{
word: "dance",
hint: "A rhythmic movement of the body often performed to music.",
},
{
word: "galaxy",
hint: "A vast system of stars, gas, and dust held together by gravity.",
},
{
word: "orchestra",
hint: "A large ensemble of musicians playing various instruments.",
},
{
word: "volcano",
hint: "A mountain or hill with a vent through which lava, rock fragments, hot vapor, and gas are ejected.",
},
{
word: "novel",
hint: "A long work of fiction, typically with a complex plot and characters.",
},
{
word: "sculpture",
hint: "A three-dimensional art form created by shaping or combining materials.",
},
{
word: "symphony",
hint: "A long musical composition for a full orchestra, typically in multiple movements.",
},
{
word: "architecture",
hint: "The art and science of designing and constructing buildings.",
},
{
word: "ballet",
hint: "A classical dance form characterized by precise and graceful movements.",
},
{
word: "astronaut",
hint: "A person trained to travel and work in space.",
},
{
word: "waterfall",
hint: "A cascade of water falling from a height.",
},
{
word: "technology",
hint: "The application of scientific knowledge for practical purposes.",
},
{
word: "rainbow",
hint: "A meteorological phenomenon that is caused by reflection, refraction, and dispersion of light.",
},
{
word: "universe",
hint: "All existing matter, space, and time as a whole.",
},
{
word: "piano",
hint: "A musical instrument played by pressing keys that cause hammers to strike strings.",
},
{
word: "vacation",
hint: "A period of time devoted to pleasure, rest, or relaxation.",
},
{
word: "rainforest",
hint: "A dense forest characterized by high rainfall and biodiversity.",
},
{
word: "theater",
hint: "A building or outdoor area in which plays, movies, or other performances are staged.",
},
{
word: "telephone",
hint: "A device used to transmit sound over long distances.",
},
{
word: "language",
hint: "A system of communication consisting of words, gestures, and syntax.",
},
{
word: "desert",
hint: "A barren or arid land with little or no precipitation.",
},
{
word: "sunflower",
hint: "A tall plant with a large yellow flower head.",
},
{
word: "fantasy",
hint: "A genre of imaginative fiction involving magic and supernatural elements.",
},
{
word: "telescope",
hint: "An optical instrument used to view distant objects in space.",
},
{
word: "breeze",
hint: "A gentle wind.",
},
{
word: "oasis",
hint: "A fertile spot in a desert where water is found.",
},
{
word: "photography",
hint: "The art, process, or practice of creating images by recording light or other electromagnetic radiation.",
},
{
word: "safari",
hint: "An expedition or journey, typically to observe wildlife in their natural habitat.",
},
{
word: "planet",
hint: "A celestial body that orbits a star and does not produce light of its own.",
},
{
word: "river",
hint: "A large natural stream of water flowing in a channel to the sea, a lake, or another such stream.",
},
{
word: "tropical",
hint: "Relating to or situated in the region between the Tropic of Cancer and the Tropic of Capricorn.",
},
{
word: "mysterious",
hint: "Difficult or impossible to understand, explain, or identify.",
},
{
word: "enigma",
hint: "Something that is mysterious, puzzling, or difficult to understand.",
},
{
word: "paradox",
hint: "A statement or situation that contradicts itself or defies intuition.",
},
{
word: "puzzle",
hint: "A game, toy, or problem designed to test ingenuity or knowledge.",
},
{
word: "whisper",
hint: "To speak very softly or quietly, often in a secretive manner.",
},
{
word: "shadow",
hint: "A dark area or shape produced by an object blocking the light.",
},
{
word: "secret",
hint: "Something kept hidden or unknown to others.",
},
{
word: "curiosity",
hint: "A strong desire to know or learn something.",
},
{
word: "unpredictable",
hint: "Not able to be foreseen or known beforehand; uncertain.",
},
{
word: "obfuscate",
hint: "To confuse or bewilder someone; to make something unclear or difficult to understand.",
},
{
word: "unveil",
hint: "To make known or reveal something previously secret or unknown.",
},
{
word: "illusion",
hint: "A false perception or belief; a deceptive appearance or impression.",
},
{
word: "moonlight",
hint: "The light from the moon.",
},
{
word: "vibrant",
hint: "Full of energy, brightness, and life.",
},
{
word: "nostalgia",
hint: "A sentimental longing or wistful affection for the past.",
},
{
word: "brilliant",
hint: "Exceptionally clever, talented, or impressive.",
},
];
In components/GameBoard.jsx
, add the code to render the main Hangman game interface, including the word display, guessed letters, and keyboard.
import { useContext, useEffect, useRef } from "react";
import GameContext from "../context/GameContext";
import GameKeyboard from "./GameKeyboard";
import { wordList } from "../constants";
// GameBoard component
const GameBoard = () => {
// Extract necessary values and functions from GameContext
const { currentWord, setCurrentWord, correctLetters, setCorrectLetters, clickedKeys, setClickedKeys, wrongGuesses, setWrongGuesses, setIsGameWon, setShowModal, isGameReset, setIsGameReset } = useContext(GameContext);
// Reference for the hint element
const hintRef = useRef();
const maxGuesses = 6;
// Effect to initialize the game board with a random word and hint
useEffect(() => {
const { word, hint } = wordList[Math.floor(Math.random() * wordList.length)];
hintRef.current.innerText = hint;
setCurrentWord(word);
setCorrectLetters(new Array(word.length).fill(""));
setIsGameReset(false);
}, [isGameReset, setCorrectLetters, setCurrentWord, setIsGameReset]);
// Effect to check game status (win/lose) based on guesses and correct letters
useEffect(() => {
if (currentWord && correctLetters.length) {
if (wrongGuesses >= maxGuesses) {
setIsGameWon(false);
setShowModal(true);
} else if (correctLetters.join("") === currentWord) {
setIsGameWon(true);
setShowModal(true);
}
}
}, [correctLetters, wrongGuesses, currentWord, setIsGameWon, setShowModal]);
// Handle key clicks and update the game state accordingly
const handleClickedKey = (clickedKey) => {
if (currentWord.includes(clickedKey)) {
const updatedCorrectLetters = correctLetters.map((letter, index) =>
currentWord[index] === clickedKey ? clickedKey : letter,
);
setCorrectLetters(updatedCorrectLetters);
} else {
setWrongGuesses((prevGuesses) => prevGuesses + 1);
}
setClickedKeys((prevKeys) => [...prevKeys, clickedKey]);
};
return (
<div>
{/* Display the current word with guessed letters */}
<ul className="flex flex-wrap items-center justify-center gap-3">
{currentWord?.split("").map((_, index) => (
<li key={index} className={`-mt-10 mb-10 w-7 text-center text-3xl font-semibold uppercase ${ !correctLetters[index] && "mt-0 border border-b-2 border-black"}`}>
{correctLetters[index]}
</li>
))}
</ul>
{/* Display the hint */}
<h4 className="mb-4 text-center text-lg font-medium max-md:text-base">
Hint:{" "}
<b ref={hintRef} className="font-semibold text-neutral-700"></b>
</h4>
{/* Display the number of incorrect guesses and max guesses */}
<h4 className="mb-4 text-center text-lg font-medium text-neutral-800 max-md:text-base">
Incorrect guesses:{" "}
<b className="font-bold text-red-500">
{wrongGuesses} / {maxGuesses}
</b>
</h4>
{/* Display the game keyboard */}
<GameKeyboard handleClickedKey={handleClickedKey} clickedKeys={clickedKeys} />
</div>
);
};
export default GameBoard;
In components/GameKeyboard.jsx
, add the code to render the virtual keyboard used for guessing letters. It displays the alphabet and triggers the guessing function when a letter is clicked.
// GameKeyboard component, receives handleClickedKey and clickedKeys as props
const GameKeyboard = ({ handleClickedKey, clickedKeys }) => {
// Create an array of alphabet letters from 'a' to 'z'
const alphabetArray = Array.from({ length: 26 }, (_, i) =>
String.fromCharCode(97 + i),
);
return (
// Container for the keyboard buttons
<div className="mt-9 flex flex-wrap justify-center gap-1">
{alphabetArray.map((char) => (
<button
onClick={() => handleClickedKey(char)}
disabled={clickedKeys.includes(char)} // Disable button if key is already clicked
key={char}
className={`w-[calc(100%/9-5px)] rounded-md border bg-emerald-700 py-1.5 font-semibold text-white hover:bg-emerald-600 ${clickedKeys.includes(char) && "pointer-events-none opacity-60"}`}
>
{char.toUpperCase()}
</button>
))}
</div>
);
};
export default GameKeyboard;
In components/HangmanIllustration.jsx
, add the code to render the Hangman graphic that updates based on the number of incorrect guesses.
import { useContext } from "react";
import GameContext from "../context/GameContext";
// HangmanIllustration component
const HangmanIllustration = () => {
// Extract wrongGuesses value from GameContext
const { wrongGuesses } = useContext(GameContext);
return (
<div>
{/* Display the hangman image based on the number of wrong guesses */}
<img
className="pointer-events-none max-w-[270px] select-none max-md:max-w-[200px]"
src={`hangman-${wrongGuesses}.svg`}
alt="Hangman Illustration"
/>
<h2 className="mt-6 text-center text-2xl font-bold uppercase max-md:hidden">Hangman Game</h2>
</div>
);
};
export default HangmanIllustration;
In components/GameOverModal.jsx
, add the code to render a modal showing the game outcome (win or loss), the correct word, and a button to play again.
import { useContext } from "react";
import GameContext from "../context/GameContext";
// GameOverModal component
const GameOverModal = () => {
// Extract necessary values from GameContext
const { currentWord, isGameWon, showModal, resetGameBoard } = useContext(GameContext);
return (
// Modal container with conditional visibility
<div className={`inset fixed z-10 flex h-full w-full items-center justify-center bg-[rgba(0,0,0,0.5)] px-3 backdrop-blur-lg ${showModal ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0"} transition-opacity duration-300`}>
<div className="flex max-w-[420px] flex-grow flex-col items-center rounded-lg border bg-white p-7 text-center shadow-2xl">
{/* Display appropriate gif based on game outcome */}
<img className="mb-5 max-w-32 max-md:w-28" src={`${showModal && (isGameWon ? 'won' : 'lost')}.gif`} alt="Gif" />
{/* Display game outcome message */}
<h4 className="text-2xl font-bold">
{showModal && (isGameWon ? "Congratulations!" : "Better Luck Next Time!")}
</h4>
{/* Display game result and the correct word */}
<p className="mb-8 mt-4 text-xl max-lg:text-lg">
{isGameWon ? "You guessed the word" : "The correct word was"}
<br />
<b className="font-bold uppercase text-emerald-700">{" "} {showModal && currentWord}</b>
</p>
{/* Display Play Again button */}
<button
onClick={resetGameBoard}
className="max-lg:2 rounded-md border bg-emerald-700 px-5 py-2.5 font-medium uppercase text-white hover:bg-emerald-600 max-lg:px-4"
>
Play Again
</button>
</div>
</div>
);
};
export default GameOverModal;
In context/GameContext.jsx
, add the code to manage the game state, including the current word, guessed letters, and game outcome, and provide this state to child components through context.
import { createContext, useState } from "react";
// Create a new context for the game
const GameContext = createContext();
// Provider component to manage game state and provide it to children components
export const GameProvider = ({ children }) => {
const [currentWord, setCurrentWord] = useState("");
const [correctLetters, setCorrectLetters] = useState([]);
const [clickedKeys, setClickedKeys] = useState([]);
const [wrongGuesses, setWrongGuesses] = useState(0);
const [isGameWon, setIsGameWon] = useState(false);
const [showModal, setShowModal] = useState(false);
const [isGameReset, setIsGameReset] = useState(false);
// Function to reset the game board to its initial state
const resetGameBoard = () => {
setShowModal(false);
setCurrentWord("");
setCorrectLetters([]);
setClickedKeys([]);
setWrongGuesses(0);
setIsGameWon(false);
setIsGameReset(true);
};
return (
// Provide the state and functions to the children components
<GameContext.Provider
value={{ currentWord, setCurrentWord, correctLetters, setCorrectLetters, clickedKeys, setClickedKeys, wrongGuesses, setWrongGuesses, isGameWon, setIsGameWon, showModal, setShowModal, resetGameBoard, isGameReset, setIsGameReset }}
>
{children}
</GameContext.Provider>
);
};
export default GameContext;
Finally, replace the content of src/App.jsx
with the provided code. It imports and renders the main components (GameBoard, GameOverModal, HangmanIllustration) and wraps them with the GameProvider for state management.
// Importing necessary components and context
import GameBoard from "./components/GameBoard";
import GameOverModal from "./components/GameOverModal";
import HangmanIllustration from "./components/HangmanIllustration";
import { GameProvider } from "./context/GameContext";
// Main application component
export default function App() {
return (
// Wrapping the app with GameProvider for state management
<GameProvider>
<div className="flex h-screen items-center justify-center px-3">
<div className="flex w-[850px] items-end justify-between gap-16 rounded-lg bg-white px-10 py-14 shadow-xl max-md:flex-col max-md:items-center max-md:px-2.5 max-md:py-8">
<HangmanIllustration />
<GameBoard />
</div>
<GameOverModal />
</div>
</GameProvider>
);
}
That’s it! If you’ve completed all the steps, you should now see your Hangman game project in your browser. You can interact with it by guessing letters, tracking your progress, and seeing the Hangman illustration update based on your guesses.
Conclusion and final words
You’ve successfully created your very own Hangman game using ReactJS and Tailwind CSS. By now, you should have a fully functional game where players can guess letters, see their progress, and enjoy a dynamic Hangman illustration. You’ve learned how to manage game state with React Context, create interactive components, and style your application with Tailwind CSS.
If you’re curious to see how the Hangman game can be built with a different approach, be sure to check out my earlier blog post on Building a Hangman Game with HTML, CSS, & JavaScript.
If you encounter any problems while creating your Hangman game, you can download the source code files for this game project by clicking the “Download” button. You can also view a live demo of it by clicking the “View Live” button.
Once you’ve downloaded the project, make sure to review the README.md
file included in the zip for detailed instructions on how to set up and run the project.
Originally published at https://www.codingnepalweb.com on July 29, 2024.