Skip to main content

How to Build a Chess Game Using HTML, CSS, and JavaScript – Step-by-Step Guide

 How to Build a Chess Game Using HTML, CSS, and JavaScript


Building a chess game using HTML, CSS, and JavaScript is an excellent project to showcase your web development skills. Chess has clear rules, a classic 8x8 grid layout, and involves interesting concepts like move validation, game state management, and user interactions, making it a perfect medium for learning and demonstrating your expertise.

In this post, we will go through:

  • Planning the project

  • Setting up the HTML

  • Styling the board with CSS

  • Writing the JavaScript logic for the game

  • Adding advanced features like move validation, piece movement, and checking for checkmates

By the end, you’ll have a fully functional chess game playable in the browser!


1. Planning the Chess Game

Before we dive into coding, let's outline what we need:

  • Chessboard: 8 rows and 8 columns with alternating black and white squares.

  • Pieces: Each piece (King, Queen, Rook, Bishop, Knight, Pawn) for both black and white sides.

  • Movement Rules: Each piece moves differently.

  • Game Logic:

    • Turn management (White and Black)

    • Move validation (legal moves only)

    • Capturing pieces

    • Detecting check, checkmate, and stalemate

Initially, we can start simple:

  • Allow moving pieces around freely (no validation)

  • Later, implement proper movement rules

  • Eventually, add check and checkmate detection


2. Setting up the HTML

First, create a basic structure:

html
<!DOCTYPE html>
<html lang="en"> <head> <meta charset="UTF-8"> <title>Chess Game</title> <link rel="stylesheet" href="style.css"> </head> <body> <h1>Chess Game</h1> <div id="chessboard"></div> <script src="script.js"></script> </body
 </html>

We have a title, a heading, and a div where the chessboard will be generated dynamically.
Simple, clean, and manageable.


3. Styling the Board with CSS

Let's make the chessboard look authentic with CSS.

css
/* style.css */ body { display: flex; flex-direction: column; align-items: center; font-family: Arial, sans-serif; background-color: #f0f0f0; height: 100vh; margin: 0; padding: 20px; } h1 { margin-bottom: 20px; } #chessboard { display: grid; grid-template-columns: repeat(8, 80px); grid-template-rows: repeat(8, 80px); border: 5px solid #333; } .square { width: 80px; height: 80px; display: flex; align-items: center; justify-content: center; font-size: 36px; cursor: pointer; } .white { background-color: #eee; } .black { background-color: #555; color: white; }

Here:

  • We define an 8x8 grid for the chessboard.

  • Alternate squares have different background colors.

  • Pieces (later inserted as Unicode symbols) will be centered.


4. Writing JavaScript to Create the Board

Now, generate the board and place the pieces.

javascript
// script.js const board = document.getElementById('chessboard'); const initialBoard = [ ['r','n','b','q','k','b','n','r'], ['p','p','p','p','p','p','p','p'], ['','','','','','','',''], ['','','','','','','',''], ['','','','','','','',''], ['','','','','','','',''], ['P','P','P','P','P','P','P','P'], ['R','N','B','Q','K','B','N','R'] ]; const pieces = { 'r': '♜', 'n': '♞', 'b': '♝', 'q': '♛', 'k': '♚', 'p': '♟', 'R': '♖', 'N': '♘', 'B': '♗', 'Q': '♕', 'K': '♔', 'P': '♙' }; function createBoard() { board.innerHTML = ''; for (let row = 0; row < 8; row++) { for (let col = 0; col < 8; col++) { const square = document.createElement('div'); square.classList.add('square'); if ((row + col) % 2 === 0) { square.classList.add('white'); } else { square.classList.add('black'); } square.dataset.row = row; square.dataset.col = col; const piece = initialBoard[row][col]; if (piece) { square.textContent = pieces[piece]; } board.appendChild(square); } } } createBoard();

This creates:

  • An 8x8 board

  • Places pieces according to standard chess setup

  • Assigns light and dark squares

At this point, we have a board with pieces!


5. Moving Pieces with JavaScript

We need to allow users to click on a piece and move it.

Let's set up basic click-to-move functionality:

javascript
let selectedSquare = null; board.addEventListener('click', (e) => { const target = e.target; if (!target.classList.contains('square')) return; if (selectedSquare) { movePiece(selectedSquare, target); selectedSquare = null; } else if (target.textContent !== '') { selectedSquare = target; } }); function movePiece(fromSquare, toSquare) { toSquare.textContent = fromSquare.textContent; fromSquare.textContent = ''; }

How this works:

  • Click a piece to select it.

  • Click another square to move it there.

Right now, there’s no validation. You can move any piece anywhere — even illegal moves.
We'll fix that next.


6. Managing Turns: White and Black

Players should alternate turns.

Add a currentPlayer variable:

javascript
let currentPlayer = 'white'; // white moves first function movePiece(fromSquare, toSquare) { const piece = fromSquare.textContent; if ((currentPlayer === 'white' && piece === piece.toLowerCase()) || (currentPlayer === 'black' && piece === piece.toUpperCase())) { alert("It's not your turn!"); return; } toSquare.textContent = fromSquare.textContent; fromSquare.textContent = ''; currentPlayer = currentPlayer === 'white' ? 'black' : 'white'; }
  • White pieces are uppercase (P, R, N, B, Q, K).

  • Black pieces are lowercase (p, r, n, b, q, k).

If you try to move when it’s not your turn, you get an alert.


7. Implementing Basic Move Validation

We must prevent invalid moves.
For simplicity, let's validate pawn movement first.

Update the movePiece function:

javascript
function movePiece(fromSquare, toSquare) { const fromRow = parseInt(fromSquare.dataset.row); const fromCol = parseInt(fromSquare.dataset.col); const toRow = parseInt(toSquare.dataset.row); const toCol = parseInt(toSquare.dataset.col); const piece = fromSquare.textContent; // Turn validation if ((currentPlayer === 'white' && piece === piece.toLowerCase()) || (currentPlayer === 'black' && piece === piece.toUpperCase())) { alert("It's not your turn!"); return; } // Basic pawn movement if (piece.toLowerCase() === '♟' || piece === '♙') { const direction = (piece === '♙') ? -1 : 1; if (fromCol === toCol && toRow === fromRow + direction && toSquare.textContent === '') { // Move one forward } else if (Math.abs(fromCol - toCol) === 1 && toRow === fromRow + direction && toSquare.textContent !== '') { // Capture } else { alert('Invalid move!'); return; } } toSquare.textContent = fromSquare.textContent; fromSquare.textContent = ''; currentPlayer = currentPlayer === 'white' ? 'black' : 'white'; }

Now, pawns:

  • Can move one step forward

  • Can capture diagonally

This is very basic, but it’s real validation!


8. Adding Other Pieces' Movements

Implementing the movement rules for each piece involves:

  • Rooks move horizontally/vertically

  • Bishops move diagonally

  • Queens combine rook and bishop movement

  • Knights move in an “L” shape

  • Kings move one square any direction

You can create functions like isValidMoveRook(fromRow, fromCol, toRow, toCol) for each piece.

Example for Knight:

javascript
function isValidMoveKnight(fromRow, fromCol, toRow, toCol) { const dx = Math.abs(fromRow - toRow); const dy = Math.abs(fromCol - toCol); return (dx === 2 && dy === 1) || (dx === 1 && dy === 2); }

Inside movePiece, call corresponding validation based on the piece.


9. Detecting Check and Checkmate

Once movement rules are perfect, detecting check and checkmate comes next.

Basic idea:

  • After a move, check if the enemy king can be captured next turn.

  • If yes, it’s a check.

  • If the king cannot escape from check, it's a checkmate.

Advanced versions involve:

  • Simulating all possible moves

  • Checking if any legal move escapes the check


10. Improving the Game: Polishing and Features

Optional Features to implement:

  • Undo last move

  • Highlight possible moves

  • Save the game to local storage

  • Timer for each player

  • Display captured pieces

  • Allow pawn promotion

  • Add sound effects

  • Responsive design for mobile screens


If code is not run

Update Code

html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Real Chess Game</title> <style> body { display: flex; flex-direction: column; align-items: center; font-family: Arial, sans-serif; background-color: #f2f2f2; margin: 0; padding: 20px; } h1 { margin-bottom: 20px; } #chessboard { display: grid; grid-template-columns: repeat(8, 80px); grid-template-rows: repeat(8, 80px); border: 4px solid #333; } .square { width: 80px; height: 80px; display: flex; align-items: center; justify-content: center; font-size: 40px; cursor: pointer; user-select: none; } .white { background-color: #eee; } .black { background-color: #555; color: white; } .selected { outline: 4px solid red; } </style> </head> <body> <h1>Real Chess Game</h1> <div id="chessboard"></div> <script> const boardElement = document.getElementById('chessboard'); // Board representation let board = [ ['r','n','b','q','k','b','n','r'], ['p','p','p','p','p','p','p','p'], ['','','','','','','',''], ['','','','','','','',''], ['','','','','','','',''], ['','','','','','','',''], ['P','P','P','P','P','P','P','P'], ['R','N','B','Q','K','B','N','R'] ]; const pieces = { 'r':'♜', 'n':'♞', 'b':'♝', 'q':'♛', 'k':'♚', 'p':'♟', 'R':'♖', 'N':'♘', 'B':'♗', 'Q':'♕', 'K':'♔', 'P':'♙' }; let currentPlayer = 'white'; let selected = null; function renderBoard() { boardElement.innerHTML = ''; for (let row = 0; row < 8; row++) { for (let col = 0; col < 8; col++) { const square = document.createElement('div'); square.className = `square ${(row + col) % 2 == 0 ? 'white' : 'black'}`; square.dataset.row = row; square.dataset.col = col; if (board[row][col]) { square.textContent = pieces[board[row][col]]; } boardElement.appendChild(square); } } } renderBoard(); boardElement.addEventListener('click', e => { const target = e.target; if (!target.classList.contains('square')) return; const row = +target.dataset.row; const col = +target.dataset.col; const piece = board[row][col]; if (selected) { if (moveIsValid(selected.row, selected.col, row, col)) { movePiece(selected.row, selected.col, row, col); switchPlayer(); } selected = null; renderBoard(); } else { if (piece && isPlayersPiece(piece)) { selected = { row, col }; target.classList.add('selected'); } } }); function movePiece(fromRow, fromCol, toRow, toCol) { board[toRow][toCol] = board[fromRow][fromCol]; board[fromRow][fromCol] = ''; } function isPlayersPiece(piece) { if (currentPlayer === 'white') return piece === piece.toUpperCase(); else return piece === piece.toLowerCase(); } function switchPlayer() { currentPlayer = currentPlayer === 'white' ? 'black' : 'white'; } // Movement validation based on piece type function moveIsValid(fromRow, fromCol, toRow, toCol) { const piece = board[fromRow][fromCol]; const target = board[toRow][toCol]; if (target && isPlayersPiece(target)) { return false; // Cannot capture own piece } const dx = toCol - fromCol; const dy = toRow - fromRow; switch (piece.toLowerCase()) { case 'p': // Pawn const dir = (piece === 'P') ? -1 : 1; if (dx === 0 && board[toRow][toCol] === '') { if (dy === dir) return true; if ((fromRow === 6 && dir === -1 || fromRow === 1 && dir === 1) && dy === dir * 2 && board[fromRow + dir][fromCol] === '') return true; } else if (Math.abs(dx) === 1 && dy === dir && board[toRow][toCol] !== '') { return true; // Capture diagonally } return false; case 'r': // Rook if (dx !== 0 && dy !== 0) return false; return pathIsClear(fromRow, fromCol, toRow, toCol); case 'n': // Knight return (Math.abs(dx) === 2 && Math.abs(dy) === 1) || (Math.abs(dx) === 1 && Math.abs(dy) === 2); case 'b': // Bishop if (Math.abs(dx) !== Math.abs(dy)) return false; return pathIsClear(fromRow, fromCol, toRow, toCol); case 'q': // Queen if (dx === 0 || dy === 0 || Math.abs(dx) === Math.abs(dy)) { return pathIsClear(fromRow, fromCol, toRow, toCol); } return false; case 'k': // King return Math.abs(dx) <= 1 && Math.abs(dy) <= 1; default: return false; } } function pathIsClear(fromRow, fromCol, toRow, toCol) { const stepRow = Math.sign(toRow - fromRow); const stepCol = Math.sign(toCol - fromCol); let currentRow = fromRow + stepRow; let currentCol = fromCol + stepCol; while (currentRow !== toRow || currentCol !== toCol) { if (board[currentRow][currentCol] !== '') return false; currentRow += stepRow; currentCol += stepCol; } return true; } </script> </body> </html>

Conclusion

Building a Chess game using HTMLCSS, and JavaScript is a fantastic way to learn:

  • DOM manipulation

  • Event handling

  • Data structure management

  • Logical thinking (movement validation)

  • Advanced gameplay features (like check and checkmate)

Start simple:
Just allow pieces to move freely.
Then step-by-step, add movement rules, turn management, checks, checkmates, and so on.


Final Project Files Summary

HTML: Layout structure
CSS: Board and pieces style
JavaScript:

  • Create and render the board

  • Manage piece movement

  • Validate moves

  • Control turns


If you want, I can also give you the full complete chess game code — including legal move validation for all pieces, checkmate detection, and more.