Canvas chessboard library with React bindings and PGN recorder (Chessbook-like feel)
npm install neo-chess-board





A modern, lightweight chess board library built with Canvas and TypeScript
Chessbook-inspired performance meets developer-friendly APIs
๐ฎ Live Demo โข ๐ Documentation โข โก Quick Start โข ๐จ Themes
---
| Feature | Neo Chess Board | Other Libraries |
|------------------------|--------------------------|----------------------|
| Bundle Size | ๐ข ~15kb (minified) | ๐ด 50-200kb |
| Dependencies | ๐ข Zero core deps | ๐ด Multiple |
| TypeScript | ๐ข Full native support | ๐ก Partial/types-only|
| React Integration | ๐ข Native hooks & SSR | ๐ด Wrapper required |
| Performance | ๐ข Canvas optimized | ๐ก DOM-heavy |
| Customization | ๐ข Themes + piece sets | ๐ด Limited options |
| PGN Support | ๐ข Built-in w/ annotations| ๐ด External library |
| Accessibility | ๐ข Optional extension | ๐ด None/limited |
- ๐ฆ Zero dependencies (React is peer dependency only)
- ๐ชถ ~15kb minified โ Minimal bundle impact
- โก 60fps animations with optimized Canvas rendering
- ๐ง Full TypeScript support with complete type definitions
- ๐ฑ Responsive design that scales beautifully
- ๐ฑ๏ธ Smooth drag & drop with customizable activation distance
- ๐จ Beautiful piece sprites with shadows and anti-aliasing
- ๐งฉ Custom piece sets โ Bring your own SVG, PNG, or Canvas images
- โจ Fluid animations with configurable duration
- ๐ฏ Legal move highlighting with dots and indicators
- ๐ Event-aware audio with per-color overrides for moves, captures, checks, and mates
- ๐ Evaluation bar that reads PGN [%eval] annotations
- ๐ Auto-flip board to follow the active player
- ๐น Visual annotations โ Draw arrows and highlight squares
- โ๏ธ React hooks ready with SSR support
- ๐
ฐ๏ธ Complete TypeScript types for everything
- ๐ Advanced PGN management โ Import/export with comments
- ๐จ Customizable themes with visual creator tool
- ๐งช Jest test suite with coverage reports
- ๐ Extensible architecture via plugin system
- ๐ Smart coordinate display โ Labels stay aligned in any orientation
- โจ๏ธ Keyboard navigation with arrow keys
- ๐ Screen reader support via optional extension
- ๐ Move history in text format
- ๐ฏ ARIA labels for all interactive elements
Install Neo Chess Board from npm:
``bash`
npm install neo-chess-boardor
yarn add neo-chess-boardor
pnpm add neo-chess-board
Import the packaged stylesheet to enable the built-in board and extension styles, then render the React component:
`tsx
import 'neo-chess-board/style.css';
import { NeoChessBoard } from 'neo-chess-board/react';
function ChessApp() {
return (
showCoordinates
highlightLegal
onMove={({ from, to, fen }) => {
console.log(Move: ${from} โ ${to});`
}}
style={{ width: '500px', height: '500px' }}
/>
);
}
`javascript
import { NeoChessBoard } from '@magicolala/neo-chess-board';
const board = new NeoChessBoard(document.getElementById('board'), {
theme: 'classic',
interactive: true,
showCoordinates: true,
highlightLegal: true,
});
board.on('move', ({ from, to, fen }) => {
console.log(Move: ${from} โ ${to});`
});
Customize the audio feedback per event or per side. Provide a single clip for all moves or tailor captures, checks, promotions, mates, and illegal move warnings for each color:
`ts`
const board = new NeoChessBoard(container, {
soundEnabled: true,
soundUrl: '/sounds/default-move.mp3',
soundEventUrls: {
move: '/sounds/quiet-move.mp3',
capture: {
white: '/sounds/white-capture.mp3',
black: '/sounds/black-capture.mp3',
},
check: '/sounds/check.mp3',
checkmate: '/sounds/mate.mp3',
promote: '/sounds/promote.mp3',
illegal: '/sounds/illegal.mp3',
},
});
When a specific clip is missing the board gracefully falls back to the move configuration (soundEventUrls.move), the color-specific defaults (soundUrls), and finally the legacy soundUrl.
Available sound event keys: move, capture, check, checkmate, promote, and illegal.
Neo Chess Board includes two beautiful themes out of the box, and you can easily create your own.
| Theme | Description | Best For |
|--------------|---------------------------|-----------------------------|
| Classic | Light & clean design | Traditional chess apps |
| Midnight | Dark & modern aesthetic | Night mode, modern UIs |
`tsx`
// Use built-in themes
Create and register your own themes:
`typescript
import { registerTheme, THEMES } from '@magicolala/neo-chess-board';
// Extend existing theme
const customTheme = {
...THEMES.midnight,
light: '#E8E8E8',
dark: '#4A4A4A',
moveFrom: 'rgba(255, 215, 0, 0.6)',
moveTo: 'rgba(0, 255, 127, 0.4)',
border: '#2C2C2C',
};
// Register for reuse
registerTheme('custom', customTheme);
// Use by name or pass directly
`
Use our Theme Creator web app to design themes visually:
- ๐๏ธ Live preview โ See changes instantly
- ๐พ Save presets โ Store themes in localStorage
- ๐ค Export code โ Generate JSON or TypeScript snippets
- ๐จ 15 customizable properties โ Full control over appearance
Try it locally: npm run dev in the demo folder, then visit http://localhost:5174/theme-creator.html
Replace default pieces with your own artwork:
`tsx
import type { PieceSet } from '@magicolala/neo-chess-board';
import whiteKing from './pieces/wK.svg';
import blackKing from './pieces/bK.svg';
const customPieces: PieceSet = {
defaultScale: 0.9,
pieces: {
K: { image: whiteKing },
k: { image: blackKing },
P: { image: whitePawn, offsetY: 0.02 },
p: { image: blackPawn, scale: 0.85 },
// ... other pieces
},
};
`
Features:
- โ
Keys follow FEN notation (K, Q, R, B, N, P for white; lowercase for black)CanvasImageSource
- โ
Any supported (SVG, PNG, Canvas elements)scale
- โ
Per-piece , offsetX, offsetY for fine-tuningboard.setPieceSet(newSet)
- โ
Runtime swapping with
- โ
Omitted pieces fall back to default sprites
`typescript`
interface NeoChessProps {
// Position & State
fen?: string; // FEN position string
orientation?: 'white' | 'black'; // Board orientation
autoFlip?: boolean; // Follow active player
// Visual Styling
theme?: ThemeName | Theme; // Built-in name or custom object
pieceSet?: PieceSet; // Custom piece images
showCoordinates?: boolean; // Show rank/file labels
// Interaction
interactive?: boolean; // Enable drag & drop
highlightLegal?: boolean; // Show legal move indicators
allowDragging?: boolean; // Enable pointer dragging
dragActivationDistance?: number; // Pixels before drag starts
allowDragOffBoard?: boolean; // Allow cancel by dropping outside
allowAutoScroll?: boolean; // Scroll container during drag
// Animations
showAnimations?: boolean; // Toggle animations
animation?: { // Animation configuration
duration?: number; // Duration in milliseconds
easing?: AnimationEasing; // Name or custom easing function
};
animationDurationInMs?: number; // Legacy duration alias
animationEasing?: AnimationEasing; // Legacy easing alias
// Arrows & Annotations
allowDrawingArrows?: boolean; // Enable right-click arrows
clearArrowsOnClick?: boolean; // Clear arrows on left click
arrows?: Arrow[]; // Controlled arrows state
onArrowsChange?: (arrows: Arrow[]) => void;
arrowOptions?: {
color?: string;
width?: number;
opacity?: number;
};
// Advanced
rulesAdapter?: RulesAdapter; // Custom chess rules
canDragPiece?: (params: {
square: Square;
piece: string;
board: NeoChessBoard;
}) => boolean;
// Events
onMove?: (move: MoveEvent) => void;
onIllegal?: (attempt: IllegalMoveEvent) => void;
onUpdate?: (state: UpdateEvent) => void;
onPromotionRequired?: (request: PromotionRequest) => void;
// Styling
style?: React.CSSProperties;
className?: string;
}
`typescript
class NeoChessBoard {
// Position Management
getPosition(): string;
getCurrentFEN(): string;
setPosition(fen: string, immediate?: boolean): void;
getMoveHistory(): string[];
submitMove(notation: string): boolean;
// Board State
getOrientation(): 'white' | 'black';
getTurn(): 'w' | 'b';
getPieceAt(square: Square): string | null;
getPieceSquares(piece: Piece): Square[];
// Move Handling
attemptMove(from: Square, to: Square, options?: {
promotion?: 'q' | 'r' | 'b' | 'n';
}): boolean;
// Promotion
previewPromotionPiece(piece: 'q' | 'r' | 'b' | 'n' | null): void;
isPromotionPending(): boolean;
getPendingPromotion(): {
from: Square;
to: Square;
color: 'w' | 'b';
mode: 'move' | 'premove';
} | null;
// Event System
on
// Animation
setAnimation(animation: { duration?: number; easing?: AnimationEasing }): void;
setAnimationDuration(duration: number): void;
// Rendering
resize(): void;
renderAll(): void;
// Runtime configuration
configure(configuration: {
drag?: { threshold?: number; snap?: boolean; ghost?: boolean; ghostOpacity?: number; cancelOnEsc?: boolean };
animation?: { durationMs?: number; easing?: AnimationEasing };
promotion?: { autoQueen?: boolean; ui?: 'dialog' | 'inline' };
}): void;
// Lifecycle
destroy(): void;
}
`
Use the animation board option or setAnimation method to adjust duration and easing at runtime.AnimationEasing accepts one of the built-in easing names ('linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out') or a custom (t: number) => number function.
Neo Chess Board offers three ways to handle pawn promotion:
#### 1. Event Listener
`typescript`
board.on('promotion', (request) => {
// Show your custom UI
showPromotionDialog().then(piece => {
request.resolve(piece); // 'q', 'r', 'b', or 'n'
});
});
#### 2. Callback Option
`typescript`
const board = new NeoChessBoard(element, {
onPromotionRequired(request) {
// Handle promotion
request.resolve('q');
}
});
#### 3. Built-in UI Extension
`typescript
import { createPromotionDialogExtension } from '@magicolala/neo-chess-board';
const board = new NeoChessBoard(element, {
extensions: [createPromotionDialogExtension()],
});
`
#### 4. Inline Overlay & Auto-Queen Controls
You can control the promotion experience directly from the board without writing a custom handler.
`typescript
const board = new NeoChessBoard(element, {
promotion: {
ui: 'inline', // show a compact overlay next to the target square
autoQueen: false, // set to true to always promote to a queen
},
});
// Update at runtime using the configure API
board.configure({ promotion: { autoQueen: true } });
`
promotion.ui defaults to 'dialog', which preserves the event/callback behaviour above. When set to 'inline' the board renders a lightweight picker on top of the board, integrated with the existing promotion preview pipeline. autoQueen resolves promotions immediately with a queen, skipping any UI or callbacks.
Neo Chess Board ships with a battle-tested game clock so you can add time controls without wiring a separate timer. Configure the
clock option with global or per-side times, increments, an initial active color, and lifecycle callbacks:
`ts
import { NeoChessBoard, createClockExtension } from '@magicolala/neo-chess-board';
const board = new NeoChessBoard(element, {
soundEnabled: false,
clock: {
initial: { w: 300_000, b: 300_000 }, // 5 minutes each
increment: 2_000,
active: 'w',
paused: true,
callbacks: {
onClockChange: (state) => console.log('tick', state.white.remaining, state.black.remaining),
onFlag: ({ color }) => console.warn(${color} flagged),${minutes}:${(seconds % 60).toString().padStart(2, '0')} ${suffix}
},
},
extensions: [
createClockExtension({
labels: { w: 'White', b: 'Black' },
highlightActive: true,
showTenths: true,
formatTime: (ms, { color }) => {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const suffix = color === 'w' ? 'โฑ๏ธ' : 'โ๏ธ';
return ;`
},
onReady(api) {
(window as typeof window & { clock?: typeof api }).clock = api;
},
}),
],
});
The board keeps the current ClockState internally. Inspect it at any time via board.getClockState() and control the timersstartClock()
with , pauseClock(), resetClock(), setClockTime(), and addClockTime(). Every change triggers strongly typed bus events so other systems can react:
- clock:change โ fires on every update with the full ClockStateclock:start
- / clock:pause โ emitted when the clock transitions between running and paused statesclock:flag
- โ dispatched once per side when a timer reaches zero
The React component exposes the same functionality:
`tsx
import { NeoChessBoard, type NeoChessRef } from '@magicolala/neo-chess-board/react';
import { useRef } from 'react';
const ref = useRef
clock={{ initial: 600_000, increment: 5_000 }}
onClockChange={(state) => console.log('remaining', state.white.remaining)}
/>;
ref.current?.startClock();
ref.current?.resetClock({ initial: { w: 300_000, b: 120_000 }, paused: true });
ref.current?.addClockTime('w', 5_000);
`
The React bindings keep the clock configuration stable across rendersโpassing the same values will not reset the timers, while changes to the configuration or callbacks are propagated automatically.
NeoChessRef now includes helpers such as getClockState, startClock, pauseClock, resetClock, setClockTime, andaddClockTime, while the component accepts onClockChange, onClockStart, onClockPause, and onClockFlag props for reactive
UIs.
`typescript
import { PgnNotation } from '@magicolala/neo-chess-board';
const pgn = new PgnNotation();
// Set metadata
pgn.setMetadata({
Event: 'World Championship',
White: 'Magnus Carlsen',
Black: 'Ian Nepomniachtchi',
Date: '2024.04.15',
});
// Add moves with annotations
pgn.addMove(1, 'e4', 'e5', "King's pawn opening.", '{%cal Ge2e4}');
pgn.addMove(2, 'Nf3', 'Nc6', 'Knights develop.', '{%csl Gf3,Gc6}');
// Export
const pgnText = pgn.toPgnWithAnnotations();
pgn.downloadPgn('game.pgn');
`
`typescript
import { Chess } from 'chess.js';
import { NeoChessBoard, ChessJsRules } from '@magicolala/neo-chess-board';
const game = new Chess();
const rules = new ChessJsRules();
const board = new NeoChessBoard(element, {
rulesAdapter: rules,
onMove: ({ from, to }) => {
const move = game.move({ from, to });
if (move) {
rules.getPgnNotation().addMove(
rules.moveNumber(),
move.san,
'Good move!',
{%cal G${from}${to}}
);
}
},
});
// Get annotated PGN
const pgn = rules.toPgn(true);
`
`typescript
// Get all attacked squares by current player
const attackedSquares = rules.getAttackedSquares();
// Check if specific square is attacked
const isAttacked = rules.isSquareAttacked('e4', 'w'); // by white
`
Make your chess board accessible to all users:
`typescript
import { createAccessibilityExtension } from '@magicolala/neo-chess-board';
const board = new NeoChessBoard(element, {
extensions: [
createAccessibilityExtension({
enableKeyboard: true,
regionLabel: 'Interactive chessboard',
})
],
});
`
Features:
- โจ๏ธ Arrow key navigation
- ๐ Screen reader compatible table
- ๐ Braille/text representation
- ๐ Move history list
- ๐ฌ ARIA labels and live regions
- ๐ฏ Coordinate notation input
`tsx
import React, { useState, useMemo } from 'react';
import { NeoChessBoard, PGNRecorder } from '@magicolala/neo-chess-board';
function ChessGame() {
const [fen, setFen] = useState
const [theme, setTheme] = useState<'classic' | 'midnight'>('midnight');
const pgn = useMemo(() => new PGNRecorder(), []);
const handleMove = ({ from, to, fen }: MoveEvent) => {
pgn.push({ from, to });
setFen(fen);
};
const exportGame = () => {
pgn.setHeaders({
Event: 'Online Game',
Site: 'My Chess App',
Date: new Date().toISOString().slice(0, 10),
});
pgn.download('my-game.pgn');
};
return (
fen={fen}
onMove={handleMove}
showCoordinates
highlightLegal
style={{ width: '100%', maxWidth: '600px' }}
/>
๐ Live Examples
Explore these interactive examples:
- ๐ Vanilla JS Starter โ Basic setup with theme switching and PGN export
- โ Chess.js Integration โ Full rules engine integration
- ๐ PGN Import & Evaluation โ Annotated games with eval bar
- โก Advanced Features โ Puzzles, analysis, keyboard controls
๐งช Testing
Neo Chess Board includes a comprehensive Jest test suite:
`bash
npm test # Run all tests
npm run test:watch # Watch mode
npm run test:coverage # Generate coverage report
`Tests cover:
- โ
Core chess engine
- โ
React component integration
- โ
PGN parsing and export
- โ
Theme system
- โ
Accessibility features
tests/README.md for details on the test structure and how to add new tests.๐๏ธ Architecture
`
Neo-Chess-Board-Ts-Library/
โโโ ๐ฏ Core Engine
โ โโโ EventBus # Type-safe event system
โ โโโ LightRules # Built-in chess rules
โ โโโ ChessJsRules # Chess.js adapter
โ โโโ NeoChessBoard # Main board class
โ
โโโ ๐จ Rendering
โ โโโ Canvas Layers # Optimized multi-layer rendering
โ โโโ FlatSprites # Default piece renderer
โ โโโ Themes # Theme system
โ
โโโ โ๏ธ React
โ โโโ NeoChessBoard # React component with hooks
โ
โโโ ๐ PGN
โ โโโ PgnNotation # PGN data structure
โ โโโ PGNRecorder # Game recording
โ
โโโ ๐ Extensions
โโโ PromotionDialog # Built-in promotion UI
โโโ Accessibility # A11y features
`๐ Performance Optimizations
- โก Canvas layering โ Separate layers for board, pieces, and highlights
- ๐ฏ Dirty rectangle tracking โ Only redraw changed regions
- ๐พ Sprite caching โ Pre-rendered piece images
- ๐ Efficient animations โ RequestAnimationFrame with interpolation
- ๐ฆ Tree-shaking friendly โ Import only what you need
- ๐งฎ Minimal re-renders โ React memo and optimization
๐ค Contributing
Contributions are welcome! Here's how to get started:
1. ๐ด Fork the repository
2. ๐ฟ Create a feature branch (
git checkout -b feature/amazing-feature)
3. โ
Write tests for your changes
4. ๐ Ensure tests pass (npm test)
5. ๐ Commit your changes (git commit -m 'Add amazing feature')
6. ๐ Push to the branch (git push origin feature/amazing-feature`)See CONTRIBUTING.md for detailed guidelines.
For demo QA expectations and release polish steps, follow the Demo QA & Release Checklist.
- ๐ Report bugs
- ๐ก Request features
- ๐ง Submit PRs
MIT ยฉ Cรฉdric Oloa
---
Made with โค๏ธ for the chess community
โญ If Neo Chess Board helps your project, consider giving it a star! โญ
๐ฎ Try the Demo โข ๐ Read the Docs โข ๐ฌ Join Discussions