Wordle Clone using HTML CSS JavaScript (Free Code)

Faraz

By Faraz -

Learn how to create a modern, responsive Wordle clone with this step-by-step tutorial. Get free source code for a fully functional word puzzle game using HTML, CSS, and JavaScript


wordle-clone-using-html-css-javascript-free-code.webp

Table of Contents

  1. Project Introduction
  2. HTML Code
  3. CSS Code
  4. JavaScript Code
  5. Conclusion
  6. Preview

Wordle took the world by storm with its simple yet addictive gameplay. If you are a developer looking to sharpen your skills in CSS Grid, Flexbox, and Async JavaScript, building a Wordle clone is the perfect project. In this guide, we provide a "Free Code Snippet" of a professional-grade Wordle clone, featuring smooth animations, a statistics tracker, and a fully responsive design.

Prerequisites

Before we dive in, ensure you have:

  • A basic understanding of HTML tags and structure.
  • Familiarity with CSS styling and animations.
  • Fundamental knowledge of JavaScript (Arrays, DOM manipulation, and LocalStorage).
  • A code editor like VS Code and a modern web browser.

Source Code

Step 1 (HTML Code):

The structure of our Wordle game is divided into three main sections: the Header, the Game Board, and the Interactive Keyboard. We also include Modals for instructions and statistics.

Step 2 (CSS Code):

To make the game eye-catching and modern, we use the Inter and Poppins font families. We utilize CSS Grid to create a perfectly aligned 5x6 board and a responsive keyboard that works on mobile devices.

:root {
    --bg: #ffffff;
    --text: #121213;
    --border: #e5e7eb;
    --absent: #71717a;
    --present: #eab308;
    --correct: #22c55e;
    --key-bg: #e5e7eb;
    --key-text: #1a1a1b;
    --tile-text: #1a1a1b;
    --tile-border: #d1d5db;
    --header-border: #f3f4f6;
    --accent: #3b82f6;
    --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
}

* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    font-family: 'Inter', sans-serif;
    -webkit-tap-highlight-color: transparent;
}

h1, h2, h3 {
    font-family: 'Poppins', sans-serif;
}

body {
    background-color: var(--bg);
    color: var(--text);
    display: flex;
    flex-direction: column;
    align-items: center;
    height: 100vh;
    /* overflow: hidden; */
}

header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    width: 100%;
    max-width: 500px;
    padding: 12px 20px;
    border-bottom: 1px solid var(--header-border);
    flex-shrink: 0;
}

header h1 {
    font-size: 1.6rem;
    font-weight: 700;
    letter-spacing: -0.5px;
    color: var(--text);
    background: linear-gradient(135deg, #121213 0%, #3b82f6 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
}

.icon-btn {
    background: #f9fafb;
    border: 1px solid var(--border);
    color: var(--text);
    font-size: 1.1rem;
    cursor: pointer;
    padding: 8px;
    border-radius: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.2s ease;
    box-shadow: var(--shadow);
}

.icon-btn:active {
    transform: scale(0.95);
}

#game-container {
    flex-grow: 1;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    align-items: center;
    width: 100%;
    max-width: 500px;
    padding: 15px;
}

#board-container {
    flex-grow: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
}

#board {
    display: grid;
    grid-template-rows: repeat(6, 1fr);
    gap: 8px;
}

.row {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 8px;
}

.tile {
    width: clamp(48px, 13vw, 62px);
    height: clamp(48px, 13vw, 62px);
    border: 2px solid var(--tile-border);
    border-radius: 8px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: clamp(1.4rem, 6vw, 2rem);
    font-weight: 700;
    text-transform: uppercase;
    user-select: none;
    transition: transform 0.1s linear, background-color 0.4s ease, border-color 0.4s ease;
}

.tile[data-state="active"] {
    border-color: #121213;
    transform: scale(1.05);
}

.tile.pop {
    transform: scale(1.15);
}

.tile.flip {
    transform: rotateX(90deg);
}

.tile[data-state="absent"] {
    background-color: var(--absent);
    border-color: var(--absent);
    color: white;
}

.tile[data-state="present"] {
    background-color: var(--present);
    border-color: var(--present);
    color: white;
}

.tile[data-state="correct"] {
    background-color: var(--correct);
    border-color: var(--correct);
    color: white;
}

#keyboard {
    display: grid;
    grid-template-rows: repeat(3, 1fr);
    gap: 8px;
    width: 100%;
    padding: 15px 0;
    flex-shrink: 0;
}

.keyboard-row {
    display: flex;
    justify-content: center;
    gap: 6px;
}

.key {
    background-color: var(--key-bg);
    color: var(--key-text);
    border: none;
    border-radius: 6px;
    height: clamp(48px, 7vh, 60px);
    flex: 1;
    display: flex;
    justify-content: center;
    align-items: center;
    font-weight: 600;
    font-size: clamp(0.75rem, 3.5vw, 1rem);
    cursor: pointer;
    user-select: none;
    text-transform: uppercase;
    transition: background-color 0.2s, transform 0.1s;
}

.key:active {
    transform: translateY(2px);
    filter: brightness(0.9);
}

.key.large {
    flex: 1.5;
    font-size: 0.8rem;
}

.key[data-state="absent"] {
    background-color: #3f3f46;
    color: white;
}

.key[data-state="present"] {
    background-color: var(--present);
    color: white;
}

.key[data-state="correct"] {
    background-color: var(--correct);
    color: white;
}

#message-container {
    position: fixed;
    top: 12%;
    left: 50%;
    transform: translateX(-50%);
    z-index: 2000;
    pointer-events: none;
    width: 90%;
    max-width: 350px;
    text-align: center;
}

.toast {
    background-color: #121213;
    color: white;
    padding: 14px 24px;
    border-radius: 12px;
    margin-bottom: 10px;
    font-weight: 600;
    font-size: 0.95rem;
    opacity: 0;
    transform: translateY(-20px);
    transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    box-shadow: 0 10px 15px -3px rgba(0,0,0,0.3);
}

.toast.show {
    opacity: 1;
    transform: translateY(0);
}

/* Modals */
.modal {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0,0,0,0.4);
    backdrop-filter: blur(4px);
    z-index: 1000;
    justify-content: center;
    align-items: center;
    padding: 20px;
}

.modal-content {
    background-color: var(--bg);
    color: var(--text);
    padding: 32px;
    border-radius: 24px;
    max-width: 420px;
    width: 100%;
    text-align: center;
    box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
    position: relative;
}

.modal h2 { 
    margin-bottom: 20px; 
    font-size: 1.5rem;
    letter-spacing: -0.5px;
}

.stats-grid {
    display: flex;
    justify-content: space-around;
    margin-bottom: 25px;
    background: #f9fafb;
    padding: 20px;
    border-radius: 16px;
}

.stat-item {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.stat-val { font-size: 1.8rem; font-weight: 700; color: #121213; }
.stat-label { font-size: 0.7rem; text-transform: uppercase; font-weight: 600; color: #71717a; }

.btn-container {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 10px;
}

.btn-primary {
    background-color: var(--correct);
    color: white;
    border: none;
    padding: 14px 24px;
    border-radius: 12px;
    cursor: pointer;
    font-weight: 700;
    font-size: 1rem;
    width: 100%;
    transition: all 0.2s ease;
    box-shadow: 0 4px 0px #16a34a;
}

.btn-primary:active {
    transform: translateY(2px);
    box-shadow: 0 2px 0px #16a34a;
}

.btn-secondary {
    background-color: #f3f4f6;
    color: #4b5563;
    border: none;
    padding: 12px;
    border-radius: 12px;
    cursor: pointer;
    font-weight: 600;
    font-size: 0.9rem;
}

.btn-danger {
    background-color: #fee2e2;
    color: #dc2626;
    border: none;
    padding: 8px;
    border-radius: 8px;
    cursor: pointer;
    font-weight: 600;
    font-size: 0.75rem;
    margin-top: 15px;
    opacity: 0.8;
}

#game-over-msg {
    font-size: 1.1rem;
    margin-bottom: 20px;
    padding: 15px;
    border-radius: 16px;
    background: #f1f5f9;
    font-weight: 500;
    line-height: 1.5;
}

@keyframes shake {
    0%, 100% { transform: translateX(0); }
    20%, 60% { transform: translateX(-6px); }
    40%, 80% { transform: translateX(6px); }
} 

Step 3 (JavaScript Code):

The JavaScript is the "brain" of the game. It handles input, validates guesses against the target word, manages animations, and saves player statistics to localStorage.

const WORD_LENGTH = 5;
const MAX_GUESSES = 6;
const WORDS = [
  'APPLE',
  'BEACH',
  'BRAIN',
  'BREAD',
  'BRUSH',
  'CHAIR',
  'CHEST',
  'CHORD',
  'CLICK',
  'CLOCK',
  'CLOUD',
  'DANCE',
  'DIARY',
  'DRINK',
  'DRIVE',
  'EARTH',
  'FEAST',
  'FIELD',
  'FRUIT',
  'GLASS',
  'GRAPE',
  'GREEN',
  'GROUND',
  'GUARD',
  'HEART',
  'HOUSE',
  'JUICE',
  'LIGHT',
  'LEMON',
  'MUSIC',
  'NIGHT',
  'OCEAN',
  'PIANO',
  'PILOT',
  'PLANE',
  'PHONE',
  'PIZZA',
  'PLANT',
  'RADIO',
  'RIVER',
  'ROBOT',
  'SHIRT',
  'SHOES',
  'SMILE',
  'SNAKE',
  'SPACE',
  'SPOON',
  'STORM',
  'TABLE',
  'TIGER',
  'TOAST',
  'TOUCH',
  'TRAIN',
  'TRUCK',
  'VOICE',
  'WATER',
  'WATCH',
  'WHALE',
  'WORLD',
  'WRITE',
  'YOUTH',
  'ZEBRA',
];

let targetWord = '';
let currentGuess = '';
let guesses = [];
let gameOver = false;
let isAnimating = false; // Flag to prevent input during animations
let stats = JSON.parse(localStorage.getItem('wordle-stats')) || {
  played: 0,
  wins: 0,
  streak: 0,
  maxStreak: 0,
};

const boardElement = document.getElementById('board');
const keyboardElement = document.getElementById('keyboard');
const messageContainer = document.getElementById('message-container');

function init() {
  targetWord = WORDS[Math.floor(Math.random() * WORDS.length)];
  createBoard();
  createKeyboard();
  setupEventListeners();
}

function createBoard() {
  boardElement.innerHTML = '';
  for (let i = 0; i < MAX_GUESSES; i++) {
    const row = document.createElement('div');
    row.className = 'row';
    for (let j = 0; j < WORD_LENGTH; j++) {
      const tile = document.createElement('div');
      tile.className = 'tile';
      tile.id = `tile-${i}-${j}`;
      row.appendChild(tile);
    }
    boardElement.appendChild(row);
  }
}

function createKeyboard() {
  const layout = [
    ['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', 'DEL'],
  ];

  keyboardElement.innerHTML = '';
  layout.forEach((rowKeys) => {
    const row = document.createElement('div');
    row.className = 'keyboard-row';
    rowKeys.forEach((key) => {
      const btn = document.createElement('button');
      btn.className =
        'key' + (key === 'ENTER' || key === 'DEL' ? ' large' : '');
      btn.textContent = key;
      btn.id = `key-${key}`;
      btn.onclick = (e) => {
        e.preventDefault();
        handleInput(key);
      };
      row.appendChild(btn);
    });
    keyboardElement.appendChild(row);
  });
}

function handleInput(key) {
  if (gameOver || isAnimating) return;

  if (key === 'ENTER') {
    submitGuess();
  } else if (key === 'DEL' || key === 'Backspace') {
    deleteLetter();
  } else if (/^[A-Z]$/i.test(key) && currentGuess.length < WORD_LENGTH) {
    addLetter(key.toUpperCase());
  }
}

function addLetter(letter) {
  if (currentGuess.length >= WORD_LENGTH) return;
  const tile = document.getElementById(
    `tile-${guesses.length}-${currentGuess.length}`
  );
  if (tile) {
    currentGuess += letter;
    tile.textContent = letter;
    tile.setAttribute('data-state', 'active');
    tile.classList.add('pop');
    setTimeout(() => tile.classList.remove('pop'), 100);
  }
}

function deleteLetter() {
  if (currentGuess.length === 0) return;
  const tile = document.getElementById(
    `tile-${guesses.length}-${currentGuess.length - 1}`
  );
  if (tile) {
    tile.textContent = '';
    tile.removeAttribute('data-state');
    currentGuess = currentGuess.slice(0, -1);
  }
}

async function submitGuess() {
  if (currentGuess.length !== WORD_LENGTH) {
    showMessage('Not enough letters');
    shakeRow(guesses.length);
    return;
  }

  isAnimating = true;
  const row = guesses.length;
  const result = checkGuess(currentGuess, targetWord);

  keyboardElement.style.pointerEvents = 'none';

  for (let i = 0; i < WORD_LENGTH; i++) {
    const tile = document.getElementById(`tile-${row}-${i}`);
    if (tile) {
      tile.classList.add('flip');
      await new Promise((r) => setTimeout(r, 200));
      tile.setAttribute('data-state', result[i]);
      tile.classList.remove('flip');
      updateKeyColor(currentGuess[i], result[i]);
    }
  }

  keyboardElement.style.pointerEvents = 'auto';
  guesses.push(currentGuess);
  isAnimating = false;

  if (currentGuess === targetWord) {
    handleWin();
  } else if (guesses.length === MAX_GUESSES) {
    handleLoss();
  }

  currentGuess = '';
}

function checkGuess(guess, target) {
  const res = Array(WORD_LENGTH).fill('absent');
  const targetArr = target.split('');
  const guessArr = guess.split('');

  for (let i = 0; i < WORD_LENGTH; i++) {
    if (guessArr[i] === targetArr[i]) {
      res[i] = 'correct';
      targetArr[i] = null;
      guessArr[i] = null;
    }
  }

  for (let i = 0; i < WORD_LENGTH; i++) {
    if (guessArr[i] && targetArr.includes(guessArr[i])) {
      res[i] = 'present';
      targetArr[targetArr.indexOf(guessArr[i])] = null;
    }
  }
  return res;
}

function updateKeyColor(letter, state) {
  const key = document.getElementById(`key-${letter}`);
  if (!key) return;
  const currentState = key.getAttribute('data-state');
  if (currentState === 'correct') return;
  if (currentState === 'present' && state === 'absent') return;
  key.setAttribute('data-state', state);
}

function handleWin() {
  gameOver = true;
  stats.played++;
  stats.wins++;
  stats.streak++;
  stats.maxStreak = Math.max(stats.streak, stats.maxStreak);
  saveStats();

  const msg = `Splendid! You found <b>${targetWord}</b>.`;
  document.getElementById('stats-title').textContent = 'CONGRATS! 🎉';
  document.getElementById('game-over-msg').innerHTML = msg;

  setTimeout(() => {
    showMessage('Winner!');
    openModal('stats-modal');
  }, 800);
}

function handleLoss() {
  gameOver = true;
  stats.played++;
  stats.streak = 0;
  saveStats();

  const msg = `Unlucky. The correct word was <b>${targetWord}</b>. Better luck next time!`;
  document.getElementById('stats-title').textContent = 'GAME OVER';
  document.getElementById('game-over-msg').innerHTML = msg;

  setTimeout(() => {
    showMessage(targetWord);
    openModal('stats-modal');
  }, 800);
}

function showMessage(msg) {
  const toast = document.createElement('div');
  toast.className = 'toast';
  toast.innerHTML = msg;
  messageContainer.appendChild(toast);
  setTimeout(() => toast.classList.add('show'), 10);
  setTimeout(() => {
    toast.classList.remove('show');
    setTimeout(() => toast.remove(), 250);
  }, 2500);
}

function shakeRow(rowIdx) {
  const rows = document.querySelectorAll('.row');
  if (rows[rowIdx]) {
    rows[rowIdx].style.animation = 'shake 0.5s';
    setTimeout(() => (rows[rowIdx].style.animation = ''), 500);
  }
}

function openModal(id) {
  document.getElementById('stat-played').textContent = stats.played;
  document.getElementById('stat-win').textContent = stats.played
    ? Math.round((stats.wins / stats.played) * 100)
    : 0;
  document.getElementById('stat-streak').textContent = stats.streak;
  document.getElementById('stat-max').textContent = stats.maxStreak;
  document.getElementById(id).style.display = 'flex';
}

function closeModal(id) {
  document.getElementById(id).style.display = 'none';
}

function saveStats() {
  localStorage.setItem('wordle-stats', JSON.stringify(stats));
}

function resetStats() {
  if (confirm('Reset your statistics? This cannot be undone.')) {
    stats = { played: 0, wins: 0, streak: 0, maxStreak: 0 };
    saveStats();
    openModal('stats-modal');
    showMessage('Progress Reset');
  }
}

function resetGame() {
  closeModal('stats-modal');
  document.getElementById('game-over-msg').innerHTML = '';
  document.getElementById('stats-title').textContent = 'STATISTICS';
  currentGuess = '';
  guesses = [];
  gameOver = false;
  isAnimating = false;
  targetWord = WORDS[Math.floor(Math.random() * WORDS.length)];
  createBoard();
  createKeyboard();
  showMessage('New Word Loaded!');
}

function setupEventListeners() {
  window.addEventListener('keydown', (e) => {
    if (e.ctrlKey || e.metaKey || e.altKey) return;
    handleInput(e.key.toUpperCase());
  });

  document.getElementById('help-btn').onclick = () => openModal('help-modal');
  document.getElementById('stats-btn').onclick = () => openModal('stats-modal');
}

window.onload = init;

Final Output:

wordle-clone-using-html-css-javascript-free-code.gif

Conclusion:

Creating a Wordle clone is an excellent way to practice modern web development techniques. This version includes advanced features like animation locking to prevent input bugs, responsive scaling for mobile users, and a persistent statistics system.

Feel free to customize the word list, adjust the colors, or add a dark mode to make it your own!

That’s a wrap!

I hope you enjoyed this post. Now, with these examples, you can create your own amazing page.

Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee

And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!

Thanks!
Faraz 😊

End of the article

Subscribe to my Newsletter

Get the latest posts delivered right to your inbox


Latest Components

Please allow ads on our site🥺