How to Build a Chessboard Component from ScratchBuilding a chessboard component from scratch is a great exercise in UI design, state management, accessibility, and performance. This guide walks you through designing and implementing a reusable, interactive chessboard component suitable for web applications using modern front-end tools. Examples will use React (functional components + hooks) and plain CSS, but the concepts apply to other frameworks.
What you’ll learn
- Project structure and requirements
- HTML/CSS layout for a responsive chessboard
- Representing the chessboard and pieces in state
- Rendering squares and pieces efficiently
- Handling user interactions (selecting, dragging, and moving)
- Validating moves (basic rules) and integrating a chess engine
- Accessibility (keyboard navigation, ARIA)
- Performance optimizations and testing
- Packaging and API design for reuse
1. Project setup and requirements
Requirements:
- A responsive 8×8 board with alternating light/dark squares
- Visual representation of pieces (SVG/PNG/fonts)
- Click and drag interactions to move pieces
- Optional: move validation, highlighting legal moves, undo/redo, FEN import/export
- Accessibility: keyboard control and screen reader support
- Reusable API: props for orientation, initial position, callbacks (onMove, onSelect), and theming
Suggested stack:
- React + TypeScript (optional)
- CSS Modules / Tailwind / styled-components for styling
- Optional: chess.js for move validation / game state
Folder structure (example)
- src/
- components/
- Chessboard/
- Chessboard.tsx
- Square.tsx
- Piece.tsx
- utils.ts
- chessboard.css
- Chessboard/
- assets/
- pieces/ (SVGs)
- App.tsx
- index.tsx
- components/
2. Data model: representing the board and pieces
Use a simple 2D array or a 1D array of 64 cells. Store pieces with a compact notation:
- type: ‘p’,‘r’,‘n’,‘b’,‘q’,‘k’
- color: ‘w’|‘b’
- Example cell value: { type: ‘k’, color: ‘w’ } or null
Common representations:
- 8×8 array: board[row][col]
- 1D array: board[rank * 8 + file]
- FEN string for import/export
Example initial position (pseudo-JS):
const initialBoard = [ ['r','n','b','q','k','b','n','r'], ['p','p','p','p','p','p','p','p'], [null,null,null,null,null,null,null,null], [null,null,null,null,null,null,null,null], [null,null,null,null,null,null,null,null], [null,null,null,null,null,null,null,null], ['P','P','P','P','P','P','P','P'], ['R','N','B','Q','K','B','N','R'], ];
(Uppercase for white, lowercase for black is a common convention.)
3. Layout and styling
Key ideas:
- Use a square container that maintains aspect ratio (padding-top trick or aspect-ratio CSS).
- Create an 8×8 grid using CSS Grid: grid-template-columns: repeat(8, 1fr);
- Size squares responsively; use CSS variables for themes.
Basic CSS example:
.chessboard { width: 100%; max-width: 600px; aspect-ratio: 1 / 1; display: grid; grid-template-columns: repeat(8, 1fr); border: 2px solid #333; } .square { position: relative; user-select: none; display: flex; align-items: center; justify-content: center; } .square.light { background: #f0d9b5; } .square.dark { background: #b58863; } .piece { width: 80%; height: 80%; pointer-events: none; }
Use SVG icons for pieces for crisp scaling. Place coordinates (a–h, 1–8) optionally in the margins.
4. Rendering the board in React
Create a Chessboard component that maps your board array to Square components.
Example (JSX-ish):
function Chessboard({ board, onSquareClick, orientation = 'white' }) { const squares = []; for (let rank = 7; rank >= 0; rank--) { for (let file = 0; file < 8; file++) { const index = rank * 8 + file; const piece = board[index]; // or board[rank][file] const isLight = (rank + file) % 2 === 0; squares.push( <Square key={index} index={index} piece={piece} isLight={isLight} onClick={() => onSquareClick(index)} /> ); } } return <div className="chessboard">{squares}</div>; }
Square component renders the piece (SVG) if present and handles click/drag events.
5. Handling interactions: select, move, drag-and-drop
Basic click-to-move flow:
- Click a piece to select → highlight legal moves
- Click a destination square to move
- Deselect on second click or outside click
Drag-and-drop:
- Use HTML5 Drag & Drop or pointer events for a smoother experience.
- On drag start, record source index and piece.
- On drop, compute destination index and call move handler.
Example click handler:
const [selected, setSelected] = useState(null); function onSquareClick(index) { const piece = board[index]; if (selected === null && piece) setSelected(index); else if (selected !== null) { movePiece(selected, index); setSelected(null); } }
For touch devices, implement touchstart/touchend and fallback to click.
6. Move validation and game rules
For full rule support (legal moves, checks, castling, en passant, promotion), integrate an established library like chess.js. It provides functions to load FEN, validate moves, and get PGN.
Minimal validation approach:
- Implement piece-specific movement patterns (pawns forward/capture, knights L-shape).
- Prevent moving onto same-color pieces.
- Optional: implement check detection by simulating moves.
Example using chess.js:
import { Chess } from 'chess.js'; const game = new Chess(); function movePiece(fromIdx, toIdx) { const from = idxToSquare(fromIdx); // e.g., 0 -> 'a1' const to = idxToSquare(toIdx); const result = game.move({ from, to, promotion: 'q' }); if (result) { // update board state from game.fen() } else { // illegal move } }
7. Visual feedback: highlights, last move, check
- Highlight selected piece and legal move squares.
- Show last-move arrow or highlighted source/destination.
- Add an overlay or icon when king is in check.
- Use CSS transitions for smooth animations.
Example CSS classes: .highlight, .legal-move, .last-move
8. Accessibility
Keyboard control:
- Allow focus on the board and arrow keys to navigate squares.
- Space/Enter to select/deselect or move.
- Announce moves and game state changes via ARIA live regions.
ARIA suggestions:
- Each square: role=“button” aria-label=“e4 white pawn”
- Use aria-pressed for selected state.
- Provide an offscreen live region: “White to move. Knight from g1 to f3.”
Ensure high-contrast theme option and respect prefers-reduced-motion.
9. Performance optimizations
- Avoid re-rendering all squares on every move: memoize Square components (React.memo) and pass stable props.
- Use requestAnimationFrame for drag animations.
- Use CSS transforms for piece movement animations (GPU-accelerated).
- Virtualization is unnecessary for an 8×8 board, but batching state updates helps.
Example of memoized Square:
const Square = React.memo(function Square({ piece, isLight, ... }) { // render });
10. Extra features
- PGN/FEN import-export
- Move history with undo/redo
- Engine integration (stockfish.js or web worker)
- Online play: socket syncing, move validation on server
- Theming: piece sets, board textures, coordinates toggle
11. Packaging and reusable API
Design props for your Chessboard component:
- initialPosition (FEN or array)
- orientation: ‘white’ | ‘black’
- draggable: boolean
- showCoordinates: boolean
- onMove({ from, to, san, fen })
- onSelect(square)
- theme: { lightColor, darkColor, pieceSet }
Example usage:
<Chessboard initialPosition="start" orientation="white" draggable onMove={(move) => console.log(move)} />
Publish as an npm package with TypeScript types, clear README, examples, and storybook stories.
12. Testing
- Unit test move logic and state updates.
- Integration tests for drag/drop and keyboard flows (Playwright, Cypress).
- Accessibility testing (axe-core).
13. Closing notes
Building a chessboard component covers UI layout, input handling, accessibility, and optional game logic. Start simple (rendering + click-to-move) and incrementally add validation, drag, and engine integration. The result can be a polished, reusable component for games, tutorials, or analysis tools.
Leave a Reply