React Wordle
📋 Table of Contents
- Project Overview
- Learning Objectives
- Prerequisites
- Project Setup
- Step-by-Step Development
- Core Components
- Game Logic Implementation
- Styling Guide
- Testing & Edge Cases
- Helpful Tips & Best Practices
- Resources & References
🎯 Project Overview
What You'll Build
A Wordle built with React and TypeScript - a word guessing game where players have 6 attempts to guess a 5-letter word with color-coded feedback.
Game Rules
- 🎮 Objective: Guess the hidden 5-letter word
- 🔢 Attempts: Maximum 6 guesses
- 🎨 Feedback System:
- 🟩 Green: Correct letter in correct position
- 🟨 Yellow: Correct letter in wrong position
- ⬜ Gray: Letter not in the word
🎓 Learning Objectives
By completing this project, you will master:
React & TypeScript Skills
- ✅ Component creation with TypeScript interfaces
- ✅ State management with typed
useState - ✅ Side effects with
useEffect - ✅ Props typing and interface definitions
- ✅ Conditional rendering with type safety
- ✅ Event handling with proper types
- ✅ Custom hooks with TypeScript
- ✅ Enum usage for constants
JavaScript & TypeScript Skills
- ✅ Type definitions and interfaces
- ✅ Generic types and utility types
- ✅ Array and string manipulation with types
- ✅ Object handling with strict typing
- ✅ Function composition with type safety
- ✅ Event listeners with proper types
- ✅ Local storage with type safety (optional)
CSS & Styling
- ✅ CSS Grid/Flexbox layouts
- ✅ CSS Modules or Styled Components
- ✅ Responsive design
- ✅ Animations and transitions
🔧 Prerequisites
Required Knowledge
- React Basics: Components, JSX, state, props
- TypeScript Fundamentals: Types, interfaces, generics
- JavaScript ES6+: Arrow functions, destructuring, array methods
- HTML/CSS: Basic styling and layout
- Git: Version control basics
Development Environment
- Node.js (v14 or higher)
- npm or yarn
- Code Editor (VS Code recommended)
- Browser with dev tools
🚀 Project Setup
1. Initialize React App with TypeScript
npx create-react-app wordle --template typescript
cd wordle
npm start2. Install Additional Dependencies
npm install classnames
npm install -D @types/node2. Recommended Project Structure
src/
├── components/
│ ├── Game/
│ │ ├── Game.tsx
│ │ └── Game.module.css
│ ├── Board/
│ │ ├── Board.tsx
│ │ └── Board.module.css
│ ├── Tile/
│ │ ├── Tile.tsx
│ │ └── Tile.module.css
│ ├── Keyboard/
│ │ ├── Keyboard.tsx
│ │ └── Keyboard.module.css
│ └── Modal/
│ ├── Modal.tsx
│ └── Modal.module.css
├── types/
│ └── game.ts
├── utils/
│ ├── wordUtils.ts
│ ├── gameLogic.ts
│ └── constants.ts
├── data/
│ └── wordlist.ts
├── hooks/
│ └── useWordle.ts
├── App.tsx
├── App.css
└── index.tsx
3. Install Additional Dependencies (Optional)
npm install classnames react-spring
npm install -D @types/classnames📝 Step-by-Step Development
Phase 1: Basic Setup and UI Structure
Step 1: Create Type Definitions
// src/types/game.ts
export interface GameGuess {
word: string;
states: TileState[];
}
export interface KeyboardKey {
key: string;
state: KeyState;
}
export enum TileState {
EMPTY = "empty",
FILLED = "filled",
CORRECT = "correct",
PRESENT = "present",
ABSENT = "absent",
}
export enum GameState {
PLAYING = "playing",
WON = "won",
LOST = "lost",
}
export enum KeyState {
UNUSED = "unused",
CORRECT = "correct",
PRESENT = "present",
ABSENT = "absent",
}
export interface GameConfig {
WORD_LENGTH: number;
MAX_ATTEMPTS: number;
KEYBOARD_ROWS: string[][];
}Step 2: Create Constants File
// src/utils/constants.ts
import { GameConfig } from "../types/game";
export const GAME_CONFIG: GameConfig = {
WORD_LENGTH: 5,
MAX_ATTEMPTS: 6,
KEYBOARD_ROWS: [
["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"],
["A", "S", "D", "F", "G", "H", "J", "K", "L"],
["ENTER", "Z", "X", "C", "V", "B", "N", "M", "BACKSPACE"],
],
};
export const ANIMATION_DURATION = 300;
export const FLIP_ANIMATION_DELAY = 100;Step 3: Create Word List with Types
// src/data/wordlist.ts
export const VALID_WORDS: readonly string[] = [
"about",
"other",
"which",
"their",
"would",
"there",
"could",
"first",
"after",
"these",
"react",
"state",
"props",
"hooks",
"event",
"style",
"class",
"mount",
"render",
"scope",
// Add more 5-letter words...
] as const;
export const TARGET_WORDS: readonly string[] = [
"react",
"state",
"props",
"hooks",
"event",
"style",
"class",
"mount",
"render",
"scope",
"array",
"async",
"await",
"break",
"catch",
"const",
"debug",
"error",
"false",
"fetch",
// Add more target words...
] as const;
export type ValidWord = (typeof VALID_WORDS)[number];
export type TargetWord = (typeof TARGET_WORDS)[number];Phase 2: Core Components Development
Step 4: Create the Tile Component
// src/components/Tile/Tile.tsx
import React from 'react';
import classNames from 'classnames';
import styles from './Tile.module.css';
import { TileState } from '../../types/game';
interface TileProps {
letter: string;
state: TileState;
delay?: number;
isRevealing?: boolean;
}
const Tile: React.FC<TileProps> = ({
letter,
state,
delay = 0,
isRevealing = false
}) => {
const tileClasses = classNames(styles.tile, styles[state], {
[styles.revealing]: isRevealing,
});
return (
<div
className={tileClasses}
style={{
animationDelay: `${delay}ms`,
transitionDelay: `${delay}ms`
}}
>
{letter.toUpperCase()}
</div>
);
};
export default Tile;Step 5: Create the Board Component
// src/components/Board/Board.tsx
import React from 'react';
import Tile from '../Tile/Tile';
import styles from './Board.module.css';
import { GAME_CONFIG, FLIP_ANIMATION_DELAY } from '../../utils/constants';
import { GameGuess, TileState } from '../../types/game';
interface BoardProps {
guesses: GameGuess[];
currentGuess: string;
currentAttempt: number;
isRevealing: boolean;
}
const Board: React.FC<BoardProps> = ({
guesses,
currentGuess,
currentAttempt,
isRevealing
}) => {
const createRow = (guess: GameGuess | string, isCurrentRow: boolean, rowIndex: number) => {
const tiles: JSX.Element[] = [];
const maxLength = GAME_CONFIG.WORD_LENGTH;
for (let i = 0; i < maxLength; i++) {
let letter = '';
let state = TileState.EMPTY;
if (typeof guess === 'string') {
// Current guess row
letter = guess[i] || '';
state = letter ? TileState.FILLED : TileState.EMPTY;
} else {
// Completed guess row
letter = guess.word[i] || '';
state = guess.states[i] || TileState.EMPTY;
}
tiles.push(
<Tile
key={`${rowIndex}-${i}`}
letter={letter}
state={state}
delay={isRevealing ? i * FLIP_ANIMATION_DELAY : 0}
isRevealing={isRevealing && rowIndex === currentAttempt}
/>
);
}
return (
<div key={rowIndex} className={styles.row}>
{tiles}
</div>
);
};
const renderBoard = (): JSX.Element[] => {
const rows: JSX.Element[] = [];
// Render completed guesses
guesses.forEach((guess, index) => {
rows.push(createRow(guess, false, index));
});
// Render current guess row
if (guesses.length < GAME_CONFIG.MAX_ATTEMPTS) {
rows.push(createRow(currentGuess, true, guesses.length));
}
// Render empty rows
for (let i = guesses.length + 1; i < GAME_CONFIG.MAX_ATTEMPTS; i++) {
rows.push(createRow('', false, i));
}
return rows;
};
return (
<div className={styles.board}>
{renderBoard()}
</div>
);
};
export default Board;Phase 3: Game Logic Implementation
Step 6: Create Game Logic Utilities
// src/utils/gameLogic.ts
import {
VALID_WORDS,
TARGET_WORDS,
ValidWord,
TargetWord,
} from "../data/wordlist";
import { TileState, GameGuess } from "../types/game";
export const getRandomWord = (): string => {
const randomIndex = Math.floor(Math.random() * TARGET_WORDS.length);
return TARGET_WORDS[randomIndex].toLowerCase();
};
export const isValidWord = (word: string): word is ValidWord => {
return VALID_WORDS.includes(word.toLowerCase() as ValidWord);
};
export const checkGuess = (guess: string, targetWord: string): TileState[] => {
const result: TileState[] = [];
const guessArray = guess.toLowerCase().split("");
const targetArray = targetWord.toLowerCase().split("");
const targetLetterCount: Record<string, number> = {};
// Count occurrences of each letter in target word
targetArray.forEach((letter) => {
targetLetterCount[letter] = (targetLetterCount[letter] || 0) + 1;
});
// First pass: Mark correct positions
guessArray.forEach((letter, index) => {
if (letter === targetArray[index]) {
result[index] = TileState.CORRECT;
targetLetterCount[letter]--;
}
});
// Second pass: Mark present/absent
guessArray.forEach((letter, index) => {
if (result[index]) return; // Already marked as correct
if (targetLetterCount[letter] > 0) {
result[index] = TileState.PRESENT;
targetLetterCount[letter]--;
} else {
result[index] = TileState.ABSENT;
}
});
return result;
};
export const isGameWon = (guess: string, targetWord: string): boolean => {
return guess.toLowerCase() === targetWord.toLowerCase();
};
export const createGameGuess = (
word: string,
states: TileState[],
): GameGuess => {
return { word, states };
};Step 7: Create Custom Hook for Game State
// src/hooks/useWordle.ts
import { useState, useEffect, useCallback } from "react";
import {
getRandomWord,
isValidWord,
checkGuess,
isGameWon,
createGameGuess,
} from "../utils/gameLogic";
import { GAME_CONFIG } from "../utils/constants";
import { GameGuess, GameState, TileState } from "../types/game";
interface UseWordleReturn {
targetWord: string;
guesses: GameGuess[];
currentGuess: string;
gameState: GameState;
error: string;
isRevealing: boolean;
addLetter: (letter: string) => void;
removeLetter: () => void;
submitGuess: () => void;
newGame: () => void;
}
export const useWordle = (): UseWordleReturn => {
const [targetWord, setTargetWord] = useState<string>("");
const [guesses, setGuesses] = useState<GameGuess[]>([]);
const [currentGuess, setCurrentGuess] = useState<string>("");
const [gameState, setGameState] = useState<GameState>(GameState.PLAYING);
const [error, setError] = useState<string>("");
const [isRevealing, setIsRevealing] = useState<boolean>(false);
// Initialize game
useEffect(() => {
newGame();
}, []);
const newGame = useCallback(() => {
setTargetWord(getRandomWord());
setGuesses([]);
setCurrentGuess("");
setGameState(GameState.PLAYING);
setError("");
setIsRevealing(false);
}, []);
const addLetter = useCallback(
(letter: string) => {
if (
currentGuess.length < GAME_CONFIG.WORD_LENGTH &&
gameState === GameState.PLAYING
) {
setCurrentGuess((prev) => prev + letter.toLowerCase());
}
},
[currentGuess.length, gameState],
);
const removeLetter = useCallback(() => {
setCurrentGuess((prev) => prev.slice(0, -1));
}, []);
const submitGuess = useCallback(() => {
if (currentGuess.length !== GAME_CONFIG.WORD_LENGTH) {
setError("Word must be 5 letters long");
setTimeout(() => setError(""), 2000);
return;
}
if (!isValidWord(currentGuess)) {
setError("Not a valid word");
setTimeout(() => setError(""), 2000);
return;
}
setIsRevealing(true);
setError("");
// Simulate animation delay
setTimeout(() => {
const guessStates = checkGuess(currentGuess, targetWord);
const newGuess = createGameGuess(currentGuess, guessStates);
setGuesses((prev) => [...prev, newGuess]);
// Check win condition
if (isGameWon(currentGuess, targetWord)) {
setGameState(GameState.WON);
} else if (guesses.length + 1 >= GAME_CONFIG.MAX_ATTEMPTS) {
setGameState(GameState.LOST);
}
setCurrentGuess("");
setIsRevealing(false);
}, 1000);
}, [currentGuess, targetWord, guesses.length]);
return {
targetWord,
guesses,
currentGuess,
gameState,
error,
isRevealing,
addLetter,
removeLetter,
submitGuess,
newGame,
};
};Phase 4: Keyboard and Input Handling
Step 7: Create Keyboard Component
// src/components/Keyboard/Keyboard.jsx
import React, { useEffect } from "react";
import styles from "./Keyboard.module.css";
import { GAME_CONFIG } from "../../utils/constants";
const Keyboard = ({ onKeyPress, guesses = [] }) => {
const getKeyState = (key) => {
// Determine key state based on previous guesses
for (let guess of guesses) {
const keyIndex = guess.word.indexOf(key.toLowerCase());
if (keyIndex !== -1) {
return guess.states[keyIndex];
}
}
return "unused";
};
const handleKeyClick = (key) => {
onKeyPress(key);
};
const handlePhysicalKeyPress = (event) => {
const key = event.key.toLowerCase();
if (key === "enter") {
onKeyPress("ENTER");
} else if (key === "backspace") {
onKeyPress("BACKSPACE");
} else if (key.match(/[a-z]/)) {
onKeyPress(key.toUpperCase());
}
};
useEffect(() => {
window.addEventListener("keydown", handlePhysicalKeyPress);
return () => window.removeEventListener("keydown", handlePhysicalKeyPress);
}, []);
return (
<div className={styles.keyboard}>
{GAME_CONFIG.KEYBOARD_ROWS.map((row, rowIndex) => (
<div key={rowIndex} className={styles.row}>
{row.map((key) => (
<button
key={key}
className={`${styles.key} ${styles[getKeyState(key)]} ${
key === "ENTER" || key === "BACKSPACE" ? styles.wide : ""
}`}
onClick={() => handleKeyClick(key)}
>
{key === "BACKSPACE" ? "⌫" : key}
</button>
))}
</div>
))}
</div>
);
};
export default Keyboard;Phase 5: Final Integration
Step 8: Create Main Game Component
// src/components/Game/Game.jsx
import React from "react";
import Board from "../Board/Board";
import Keyboard from "../Keyboard/Keyboard";
import Modal from "../Modal/Modal";
import { useWordle } from "../../hooks/useWordle";
import { GAME_STATES } from "../../utils/constants";
import styles from "./Game.module.css";
const Game = () => {
const {
guesses,
currentGuess,
gameState,
error,
addLetter,
removeLetter,
submitGuess,
newGame,
targetWord,
} = useWordle();
const handleKeyPress = (key) => {
if (gameState !== GAME_STATES.PLAYING) return;
if (key === "ENTER") {
submitGuess();
} else if (key === "BACKSPACE") {
removeLetter();
} else if (key.match(/[A-Z]/)) {
addLetter(key);
}
};
const getModalMessage = () => {
switch (gameState) {
case GAME_STATES.WON:
return "Congratulations! You guessed the word!";
case GAME_STATES.LOST:
return `Game Over! The word was: ${targetWord.toUpperCase()}`;
default:
return "";
}
};
return (
<div className={styles.game}>
<header className={styles.header}>
<h1>Wordle </h1>
<button onClick={newGame} className={styles.newGameBtn}>
New Game
</button>
</header>
{error && <div className={styles.error}>{error}</div>}
<Board
guesses={guesses}
currentGuess={currentGuess}
currentAttempt={guesses.length}
/>
<Keyboard onKeyPress={handleKeyPress} guesses={guesses} />
{gameState !== GAME_STATES.PLAYING && (
<Modal message={getModalMessage()} onClose={newGame} />
)}
</div>
);
};
export default Game;🎨 Styling Guide
CSS Variables for Consistency
/* src/App.css */
:root {
--color-correct: #6aaa64;
--color-present: #c9b458;
--color-absent: #787c7e;
--color-empty: #ffffff;
--color-filled: #d3d6da;
--color-border: #d3d6da;
--color-text: #1a1a1b;
--color-background: #ffffff;
--font-family: "Helvetica Neue", Arial, sans-serif;
--border-radius: 4px;
--transition: all 0.3s ease;
}Responsive Design Tips
/* Mobile-first approach */
.game {
max-width: 500px;
margin: 0 auto;
padding: 20px;
}
@media (max-width: 768px) {
.tile {
width: 50px;
height: 50px;
font-size: 24px;
}
}🧪 Testing & Edge Cases
Manual Testing Checklist
- [ ] Valid 5-letter word submission
- [ ] Invalid word rejection
- [ ] Keyboard input (physical & virtual)
- [ ] Duplicate letters handling
- [ ] Win condition
- [ ] Lose condition
- [ ] New game functionality
- [ ] Responsive design
Edge Cases to Handle
- Empty input submission
- Words with repeated letters
- Non-alphabetic characters
- Case sensitivity
- Rapid key presses
💡 Helpful Tips & Best Practices
React Best Practices
- Use functional components with hooks
- Keep components small and focused
- Use meaningful names for variables and functions
- Implement proper error boundaries
- Use React.memo for performance optimization
Code Organization
// Good: Destructure props
const Tile = ({ letter, state, delay }) => {
// Component logic
};
// Good: Use early returns
const validateInput = (input) => {
if (!input) return false;
if (input.length !== 5) return false;
return true;
};Performance Tips
- Use useCallback for event handlers
- Implement virtual scrolling for large word lists
- Lazy load components when possible
- Optimize re-renders with React.memo
Debugging Tips
- Use React DevTools for component inspection
- Add console.logs strategically
- Use browser debugger with breakpoints
- Test edge cases thoroughly
📚 Resources & References
Official Documentation
CSS & Styling
Development Tools
Inspiration & Examples
✅ Project Completion Checklist
Core Features
- [ ] Game board with 6 rows of 5 tiles
- [ ] Color-coded feedback system
- [ ] Virtual keyboard
- [ ] Physical keyboard support
- [ ] Win/lose conditions
- [ ] New game functionality
Code Quality
- [ ] Well-organized component structure
- [ ] Proper state management
- [ ] Error handling
- [ ] Performance optimizations
- [ ] Code comments and documentation
User Experience
- [ ] Responsive design
- [ ] Smooth animations
- [ ] Clear feedback messages
- [ ] Intuitive controls
- [ ] Accessibility features
Testing
- [ ] Manual testing completed
- [ ] Edge cases handled
- [ ] Cross-browser compatibility
- [ ] Mobile responsiveness
🎉 Congratulations
You've successfully built a complete Wordle using React! This project demonstrates your understanding of:
- Modern React development patterns
- State management and component lifecycle
- User interface design and interaction
- JavaScript logic and problem-solving
- CSS styling and responsive design
Keep building and learning! 🚀
Created by: Asteroid Studio
Author: abhidahal
Last Updated: July 2025
Version: 1.0.0