🏁 Neo Chess Board
✨ Features
🎯 Modern & Lightweight
- 📦 Zero dependencies (React is peer dependency)
- 🪶 Minimal bundle size
- ⚡ High performance Canvas rendering
- 🔧 Full TypeScript support
🎮 Rich Chess Experience
- 🖱️ Smooth drag & drop interactions
- 🎨 Beautiful piece sprites with shadows
- ✨ Fluid animations and transitions
- 🎯 Legal move highlighting
- 🔄 Optional auto-flip to follow the side to move
- 📱 Responsive design
🔧 Developer Friendly
- 🅰️ Complete TypeScript types
- ⚛️ React hooks ready
- 📋 Advanced PGN Management (import/export with annotations)
- 🎨 Customizable themes
- 🧪 Jest setup for automated testing
🎪 Advanced Features
- 📝 Built-in PGN recorder
- 🎭 Multiple visual themes
- 🔄 FEN support
- 🎮 Custom rules engine
- 🏹 Visual PGN Annotations (arrows & circles)
- 📐 Square names stay aligned to the bottom and left edges in every orientation
🚀 Quick Start
Installation
Configure npm to use the GitHub Packages registry for the @magicolala
scope before installing. Generate a GitHub Personal Access Token with the read:packages
permission and add it to your .npmrc
:
# .npmrc
@magicolala:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
Replace ${GITHUB_TOKEN}
with your token or an environment variable. Then install the package:
npm install @magicolala/neo-chess-board
# or
yarn add @magicolala/neo-chess-board
# or
pnpm add @magicolala/neo-chess-board
React Usage
import React, { useState } from 'react';
import { NeoChessBoard } from '@magicolala/neo-chess-board/react';
function ChessApp() {
const [fen, setFen] = useState();
return (
<NeoChessBoard
theme="midnight"
onMove={({ from, to, fen }) => {
console.log(`Move: ${from} → ${to}`);
setFen(fen);
}}
style={{ width: '400px', height: '400px' }}
/>
);
}
Vanilla JavaScript
import { NeoChessBoard } from '@magicolala/neo-chess-board';
const board = new NeoChessBoard(document.getElementById('board'), {
theme: 'classic',
interactive: true,
showCoordinates: true,
});
board.on('move', ({ from, to, fen }) => {
console.log(`Move: ${from} → ${to}`);
});
🎨 Themes
Neo Chess Board comes with beautiful built-in themes:
Theme | Preview | Colors |
---|---|---|
Classic | Light & clean design | |
Midnight | Dark & modern feel |
Use registerTheme('sunset', customTheme)
to add reusable presets. Custom theme objects can also be passed directly to constructors, setTheme
, or the React component.
🌐 Theme Creator Web App
Designing colors manually can be slow. Visit the interactive Theme Creator to preview palettes live and export the resulting code. The builder includes:
- 🎛️ Real-time controls for every theme property (board colors, highlights, arrows, etc.)
- 💾 Quick access to existing presets so you can start from
classic
ormidnight
- 📤 Export helpers that copy JSON or TypeScript snippets using
registerTheme
Try it out
- Open the hosted Theme Creator. For local work run
npm run dev
insidedemo/
and navigate tohttp://localhost:5174/theme-creator.html
. - Choose a base theme, tweak the color pickers, and watch the preview board update instantly.
- Name the palette, save it, and download the generated JSON or TypeScript code.
You can then register the exported object in your project:
import { registerTheme } from '@magicolala/neo-chess-board';
const aurora = {
light: '#F5F3FF',
dark: '#1E1B4B',
// ...rest of the properties from the generator
};
registerTheme('aurora', aurora);
The Theme Creator keeps saved palettes in localStorage
, so you can revisit and refine them anytime.
📖 Documentation
Core Components
NeoChessBoard (React)
interface NeoChessProps {
fen?: string; // Chess position in FEN notation
position?: string; // Alias for FEN when integrating with other APIs
theme?: ThemeName | Theme; // Built-in theme name or custom object
orientation?: 'white' | 'black'; // Board orientation
boardOrientation?: 'white' | 'black'; // Orientation alias
chessboardRows?: number; // Number of ranks to render
chessboardColumns?: number; // Number of files to render
interactive?: boolean; // Enable drag & drop
showCoordinates?: boolean; // Show file/rank labels
animationMs?: number; // Animation duration (legacy alias)
animationDurationInMs?: number; // Preferred animation duration alias
showAnimations?: boolean; // Toggle move animations
highlightLegal?: boolean; // Highlight legal moves
allowDragging?: boolean; // Enable pointer dragging
dragActivationDistance?: number; // Pixels required before a drag starts
allowDragOffBoard?: boolean; // Allow dropping outside the board to cancel
allowAutoScroll?: boolean; // Auto-scroll parent containers near edges
allowDrawingArrows?: boolean; // Enable right-click arrow drawing
clearArrowsOnClick?: boolean; // Clear arrows with a left click
arrowOptions?: { color?: string; width?: number; opacity?: number }; // Default arrow styling
arrows?: Arrow[]; // Controlled arrow collection
onArrowsChange?: (arrows: Arrow[]) => void; // Called when arrows change
canDragPiece?: (params: { square: Square; piece: string; board: NeoChessBoard }) => boolean; // Filter draggable pieces
onMove?: (move) => void; // Move event handler
onIllegal?: (attempt) => void; // Illegal move handler
style?: React.CSSProperties; // CSS styling
className?: string; // CSS class
}
Core Chess Engine
// Initialize board
const board = new NeoChessBoard(element, options);
// Event handling
board.on('move', ({ from, to, fen }) => {
// Handle move
});
board.on('illegal', ({ from, to, reason }) => {
// Handle illegal move attempt
});
// Position management
board.setPosition('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1');
const currentFEN = board.getPosition();
PGN Recording & Annotations
import { PgnNotation } from '@magicolala/neo-chess-board';
const pgn = new PgnNotation();
// Set game metadata
pgn.setMetadata({
Event: 'Annotated Game',
White: 'Player A',
Black: 'Player B',
Date: '2024.09.15'
});
// Add moves with comments and visual annotations
pgn.addMove(1, 'e4', 'e5', 'White starts with king's pawn.', '{%cal Ge2e4,Re7e5}');
pgn.addMove(2, 'Nf3', 'Nc6', 'Knights develop.', '{%csl Gf3,Gc6}');
// Generate PGN with annotations
const pgnText = pgn.toPgnWithAnnotations();
console.log(pgnText);
/*
[Event "Annotated Game"]
[Site "Neo Chess Board"]
[Date "2024.09.15"]
[Round "1"]
[White "Player A"]
[Black "Player B"]
[Result "*"]
1. e4 {%cal Ge2e4,Re7e5} e5 {White starts with king's pawn.}
2. Nf3 {%csl Gf3,Gc6} Nc6 {Knights develop.}
*/
// Download PGN
pgn.downloadPgn('annotated_game.pgn');
🎪 Advanced Examples
Complete Chess Application
import React, { useState, useMemo } from 'react';
import { PGNRecorder } from '@magicolala/neo-chess-board';
import { NeoChessBoard } from '@magicolala/neo-chess-board/react';
function ChessGame() {
const [fen, setFen] = useState();
const [theme, setTheme] = useState('midnight');
const pgn = useMemo(() => new PGNRecorder(), []);
const handleMove = ({ from, to, fen }) => {
pgn.push({ from, to });
setFen(fen);
};
const exportGame = () => {
pgn.setHeaders({
Event: 'Online Game',
Site: 'My App',
Date: new Date().toISOString().slice(0, 10),
});
pgn.download();
};
return (
<div className="chess-game">
<div className="board-controls">
<button onClick={() => setTheme('classic')}>Classic</button>
<button onClick={() => setTheme('midnight')}>Midnight</button>
<button onClick={exportGame}>Export PGN</button>
</div>
<NeoChessBoard
theme={theme}
fen={fen}
onMove={handleMove}
showCoordinates
style={{ width: '100%', maxWidth: '500px' }}
/>
<div className="game-info">
<textarea value={pgn.getPGN()} readOnly />
</div>
</div>
);
}
Custom Themes
import { NeoChessBoard, THEMES, registerTheme } from '@magicolala/neo-chess-board';
// Extend existing theme
const customTheme = {
...THEMES.midnight,
moveFrom: 'rgba(255, 215, 0, 0.6)', // Golden highlight
moveTo: 'rgba(0, 255, 127, 0.4)', // Spring green
};
// Optionally register for later use
registerTheme('sunset', customTheme);
const board = new NeoChessBoard(element, {
// Apply directly with an object
theme: customTheme,
});
board.setTheme('sunset');
Integration with Chess.js
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);
// Add annotations to the last move
rules.getPgnNotation().addMoveAnnotations(rules.moveNumber(), true, {
arrows: [{ from: move.from, to: move.to, color: '#00ff00' }],
circles: [{ square: move.to, color: '#ffff00' }],
textComment: 'Good move!',
});
}
},
});
// To get the PGN with annotations from ChessJsRules:
const pgnWithAnnotations = rules.toPgn(true);
console.log(pgnWithAnnotations);
🏗️ Architecture
Neo-Chess-Board-Ts-Library/
├── 🎯 Core Engine
│ ├── NeoChessBoard # Main board class
│ ├── BoardDomManager # Canvas & DOM layout orchestration
│ ├── BoardEventManager # Pointer + keyboard routing
│ ├── BoardAudioManager # Move sound lifecycle
│ ├── EventBus # Type-safe event system
│ ├── LightRules # Built-in chess rules
│ └── Utils # Chess utilities
├── 🎨 Rendering
│ ├── FlatSprites # SVG-like piece rendering
│ ├── Themes # Visual theme system
│ └── Canvas Layers # Optimized rendering
├── ⚛️ React Integration
│ └── NeoChessBoard # React component wrapper
└── 📝 PGN Support
└── PGNRecorder # Game notation recording
Internal helper managers
The refactored core is split into focused helpers so you can reason about behaviour changes quickly:
BoardDomManager
builds and maintains the layered canvas/DOM structure, applies inline styles, and wires resize logic.BoardEventManager
centralises pointer/keyboard routing so interaction tweaks stay isolated from rendering concerns.BoardAudioManager
owns move sound configuration, live toggles, and per-colour playback.
Each manager exposes a small API and is orchestrated by NeoChessBoard
, making it easier to customise or swap subsystems without touching unrelated code.
🎯 Why Neo Chess Board?
Feature | Neo Chess Board | Other Libraries |
---|---|---|
Bundle Size | 🟢 ~15kb | 🔴 50-200kb |
TypeScript | 🟢 Full support | 🟡 Partial |
React Ready | 🟢 Native hooks | 🔴 Wrapper needed |
Performance | 🟢 Canvas optimized | 🟡 DOM heavy |
Themes | 🟢 Built-in + custom | 🔴 Limited |
PGN Export | 🟢 Included | 🔴 External dep |
Modern Code | 🟢 ES2022+ | 🔴 Legacy |
📋 API Reference
React Component Props
interface NeoChessProps {
// Position & Rules
fen?: string; // Position in FEN notation
rulesAdapter?: RulesAdapter; // Custom rules engine
// Visual Appearance
theme?: ThemeName | Theme; // Built-in theme name or custom object
orientation?: 'white' | 'black'; // Board flip
autoFlip?: boolean; // Automatically follow the side to move
showCoordinates?: boolean; // A-H, 1-8 labels (always bottom/left)
// Interaction
interactive?: boolean; // Enable piece dragging
highlightLegal?: boolean; // Show legal move dots
animationMs?: number; // Move animation speed (legacy alias)
animationDurationInMs?: number; // Preferred animation duration alias
showAnimations?: boolean; // Toggle move animations
allowDragging?: boolean; // Enable pointer dragging interactions
dragActivationDistance?: number; // Minimum pointer travel before dragging starts
allowDragOffBoard?: boolean; // Allow dropping outside the board to cancel
allowAutoScroll?: boolean; // Auto-scroll scrollable ancestors near edges
allowDrawingArrows?: boolean; // Enable right-click arrow drawing
clearArrowsOnClick?: boolean; // Clear arrows on left click
arrowOptions?: { color?: string; width?: number; opacity?: number }; // Default arrow styling
arrows?: Arrow[]; // Controlled arrows collection
onArrowsChange?: (arrows: Arrow[]) => void; // Change callback for arrows
canDragPiece?: (params: { square: Square; piece: string; board: NeoChessBoard }) => boolean; // Control drag eligibility
// Event Handlers
onMove?: (move: MoveEvent) => void;
onIllegal?: (attempt: IllegalMoveEvent) => void;
onUpdate?: (state: UpdateEvent) => void;
// Styling
style?: React.CSSProperties;
className?: string;
}
Core Board Methods
class NeoChessBoard {
constructor(element: HTMLElement, options?: BoardOptions);
// Position Management
getPosition(): string;
setPosition(fen: string, immediate?: boolean): void;
// Event System
on<T>(event: string, handler: (data: T) => void): () => void;
// Rendering
resize(): void;
renderAll(): void;
// Cleanup
destroy(): void;
}
🧪 Testing
Neo Chess Board ships with a Jest-based test environment that covers the core engine, React bindings, and demo scenarios. The suite lives under tests/
, and tests/README.md
describes the folder structure together with guidance for adding additional cases. Running the suite locally will also refresh helper artifacts such as tests/RESULTS.md
and the coverage output.
npm test # Run the full suite once
npm run test:watch # Re-run affected tests on file changes
npm run test:coverage # Produce an updated coverage summary + HTML report
ℹ️ Coverage numbers depend on your latest local execution. After running the commands above you can open
coverage/lcov-report/index.html
for detailed metrics or commit an updated summary totests/RESULTS.md
if you capture a new campaign.
🚀 Performance
- Smooth 60fps animations
- Optimized Canvas rendering with layers
- Efficient piece sprite system
- Minimal re-renders in React
- Memory efficient event system
🌟 Examples Gallery
Check out these live examples powered by Neo Chess Board:
- 🌐 Vanilla JS Starter – Quick start board with theme switching, move history, and PGN export helpers.
- ♞ Chess.js Integration – Demonstrates the ChessJsRules adapter synchronized with the chess.js engine.
- 📈 PGN + Evaluation HUD – Import annotated games and follow the evaluation bar as you navigate.
- ⚡ Advanced Features Showcase – Explore puzzles, analysis tools, and keyboard-driven workflows.
🤝 Contributing
We love contributions! See CONTRIBUTING.md for details.
📄 License
MIT © Cédric Oloa