Professional chess puzzle widget using chessground and chess.js. Create interactive chess experiences like Lichess with just 2 files - completely standalone and offline-ready.
Try the interactive demo here!
Experience 20+ real Lichess puzzles ranging from ELO 600 to 2400, featuring:
- ♟️ Classic tactical themes (Forks, Pins, Discovered Attacks)
- 🤖 Stockfish AI counter-move feedback with visual arrows
- 🌍 Multi-language support (English/German)
- 🎭 Premove system for Lichess-style puzzle imports
Perfect for: Puzzle websites, chess training apps, blog articles, educational platforms
Visual puzzle creation tool with live preview!
Create custom chess puzzles with our interactive builder - no coding required! Features drag-and-drop board setup, solution recording, Lichess imports, and instant HTML export.
Try the puzzle builder live here!
Learn more about the Builder →
- ♟️ Interactive Chess Puzzles - Drag & drop pieces with solution validation
- 🎭 Premove Support - Automatic opponent moves before puzzle starts (perfect for Lichess imports!)
- 🔀 Alternative Solutions - Support multiple valid solution paths with
|separator - 🌍 Internationalization - Multi-language support (English, German, extensible)
- 📱 Responsive Design - Works perfectly on desktop and mobile
- 🎨 Professional UI - Lichess-quality board and piece graphics
- ⚡ Zero Dependencies - No CDN calls, works completely offline
- 🔧 Easy Integration - Just include 2 files in your HTML
- 🚀 Modern Development - Vite dev server with live reload
- 🎯 Multiple Widgets - Add multiple boards to the same page
- 🤖 Stockfish Integration - AI-powered counter-move feedback for wrong moves
- 🏹 Visual Arrows - Red arrows showing Stockfish's counter-moves
- 💾 Smart Caching - localStorage caching reduces API calls by >80%
- ⏸️ Wrong Move Retention - Optionally keep mistakes visible for analysis
- ❓ Question Mark Indicator - Visual marker on wrong move squares
- 🎯 Dual Arrows - Yellow arrow (your move) + Red arrow (counter-move)
- 📊 State Management - Track puzzle progress with event system
- 🔔 Event System - Subscribe to state changes, moves, and puzzle completion
- 🔄 Manual Revert - Undo wrong moves with button control
- 📈 State History - Complete history of all state transitions
- 🎮 Public API - Full programmatic control over puzzle state
- 🔄 Free Play Mode - Analysis mode without puzzle constraints
- 🎨 Custom Pieces - Use your own piece images with flexible naming conventions
- ⚙️ Highly Configurable - FEN positions, solutions, board sizes, languages, AI feedback
npm install
npm run buildCopy these generated files to your website:
dist/chess-widget.min.js(production)dist/chess-widget.min.css(production)
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="chess-widget.min.css">
</head>
<body>
<!-- Your chess puzzle -->
<div class="chess-puzzle"
data-fen="r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3"
data-solution="d2d4,e5d4,c2c3"
data-width="400">
</div>
<script src="chess-widget.min.js"></script>
</body>
</html>The widget will work immediately with all dependencies bundled - no internet connection required!
Use these data attributes to configure your chess widgets:
| Attribute | Required | Description | Example |
|---|---|---|---|
data-fen |
Yes | Chess position in FEN notation | "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" |
data-solution |
No | Solution moves (comma-separated). Use | for alternative paths |
"e2e4,e7e5,Nf3|e2e4,e7e5,Bc4" |
data-premove-enabled |
No | First move is auto-played as opponent's move (default: false) | "true" |
data-width |
No | Board width in pixels (default: 400) | "500" |
data-auto-flip |
No | Auto-rotate board for black's turn (default: false) | "true" |
data-orientation |
No | Fixed board orientation: 'white' or 'black' | "black" |
data-lang |
No | Language code: 'en' (English) or 'de' (German) | "de" |
| Attribute | Required | Description | Example |
|---|---|---|---|
data-stockfish-enabled |
No | Enable AI counter-move feedback (default: false) | "true" |
data-stockfish-depth |
No | AI analysis depth, 1-20 (default: 12) | "15" |
data-stockfish-timeout |
No | API timeout in milliseconds (default: 2000) | "3000" |
data-stockfish-show-arrow |
No | Show red arrow for counter-moves (default: true) | "true" |
data-stockfish-show-animation |
No | Animate counter-moves (default: true) | "true" |
data-stockfish-cache-enabled |
No | Enable localStorage caching (default: true) | "true" |
| Attribute | Required | Description | Example |
|---|---|---|---|
data-expose-state-events |
No | Enable external state event system (default: true) | "true" |
data-retain-wrong-moves |
No | Keep wrong moves visible until manual undo (default: false) | "true" |
| Attribute | Required | Description | Example |
|---|---|---|---|
data-pieces-url |
No | Path to folder containing piece images | "./pieces/custom" |
data-pieces-prefix |
No | Filename prefix for multiple sets in one folder | "s1_" |
data-pieces-format |
No | Naming convention: 'lichess' (wK), 'uppercase' (WK) (default: lichess) | "uppercase" |
data-pieces-ext |
No | File extension: 'png', 'svg', 'webp' (default: svg) | "png" |
data-pieces-scale |
No | Scale factor 0.5-1.5 for piece size adjustment (default: 1.0) | "0.9" |
<div class="chess-puzzle"
data-fen="r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/3P1N2/PPP2PPP/RNBQK2R w KQkq - 0 1"
data-solution="Qd1h5,Kg8h8,Qh5h7">
</div><div class="chess-puzzle"
data-fen="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1">
</div><div class="chess-puzzle"
data-fen="rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"
data-auto-flip="true">
</div><div class="chess-puzzle"
data-fen="r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3"
data-width="600">
</div><!-- Puzzle with multiple correct paths -->
<div class="chess-puzzle"
data-fen="r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 1"
data-solution="f1e1,e5e4,d1h5|f1e1,e5e4,d1f3">
<!-- Both Qh5 and Qf3 are correct after Re1 -->
</div><!-- Puzzle with German interface -->
<div class="chess-puzzle"
data-fen="rnb3kr/p4ppp/8/8/8/8/5PPP/4R1K1 w - - 0 1"
data-solution="e1e8"
data-lang="de">
</div><!-- Puzzle with AI counter-move feedback and arrows -->
<div class="chess-puzzle"
data-fen="r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 1"
data-solution="d1e2,f6e4,e2e4"
data-stockfish-enabled="true"
data-stockfish-depth="12"
data-stockfish-show-arrow="true">
<!-- Wrong moves trigger Stockfish counter-move with red arrow -->
</div><!-- Puzzle with opponent's premove (common in Lichess imports) -->
<div class="chess-puzzle"
data-fen="rnbqkb1r/pppp1ppp/5n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 2 3"
data-solution="d2d4,e5d4,c2c3"
data-premove-enabled="true"
data-orientation="white">
<!-- First move (d2d4) is auto-played as opponent's move -->
<!-- Board automatically flips to show player's perspective -->
<!-- Perfect for imported Lichess puzzles! -->
</div><!-- Puzzle with state tracking -->
<div class="chess-puzzle" id="my-puzzle"
data-fen="r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 1"
data-solution="f3e5,f6e4,e1g1"
data-expose-state-events="true">
</div>
<script>
// Subscribe to state changes
const puzzle = document.getElementById('my-puzzle').widgetState;
puzzle.on('stateChange', ({ previous, current }) => {
console.log(`State: ${previous} → ${current}`);
});
puzzle.on('puzzleSolved', () => {
console.log('Congratulations!');
});
puzzle.on('wrongMove', ({ move }) => {
console.log(`Wrong move: ${move}`);
});
</script><!-- Puzzle with wrong move retention -->
<div class="chess-puzzle"
data-fen="r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 1"
data-solution="f3e5,f6e4,e1g1"
data-retain-wrong-moves="true"
data-stockfish-enabled="true"
data-stockfish-show-arrow="true">
<!-- Wrong moves stay visible with question mark indicator -->
<!-- Shows dual arrows: yellow (your move) + red (counter-move) -->
<!-- Click "Undo Wrong Move" button to try again -->
</div><!-- Use custom PNG pieces -->
<div class="chess-puzzle"
data-fen="r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 1"
data-solution="f3g5,d7d5,e4d5"
data-pieces-url="./pieces/custom"
data-pieces-ext="png">
<!-- Expects files: wK.png, wQ.png, bK.png, bQ.png, etc. -->
</div>
<!-- Multiple piece sets in one folder using prefix -->
<div class="chess-puzzle"
data-fen="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
data-pieces-url="./pieces"
data-pieces-prefix="wood_"
data-pieces-ext="png"
data-pieces-scale="0.9">
<!-- Expects files: wood_wK.png, wood_wQ.png, wood_bK.png, etc. -->
</div>Access the widget instance via element.widgetInstance:
const widget = document.querySelector('.chess-puzzle').widgetInstance;
// State management
widget.getState() // Get current state
widget.on(event, callback) // Subscribe to events
widget.off(event, callback) // Unsubscribe from events
widget.getStateHistory() // Get complete state history
// Wrong move control
widget.hasWrongMove() // Check if wrong move is retained
widget.revertWrongMove() // Manually undo wrong move
widget.getWrongMoveData() // Get wrong move details| Event | Trigger | Data |
|---|---|---|
stateChange |
Any state transition | { previous, current, metadata } |
moveAttempted |
Before move validation | { from, to } |
correctMove |
Valid move made | { move } |
wrongMove |
Invalid move made | { move } |
puzzleSolved |
Puzzle completed | {} |
puzzleReset |
Reset button clicked | {} |
| State | Description |
|---|---|
not_started |
Initial state, no moves made |
in_progress |
At least one correct move made |
wrong_move |
User made incorrect move (transitional) |
solved |
Puzzle successfully completed |
For widget development with live reload:
git clone <repository>
cd chess-widget
npm install
npm run dev # Starts Vite dev server with live reload- TypeScript - Full type safety for widget and builder
- Rollup - Bundles TypeScript widget into standalone IIFE
- Vite - Fast dev server with hot reload
The Rollup build automatically:
- Compiles TypeScript: All source files in
src/are TypeScript - Bundles Dependencies: Combines chess.js and chessground into the widget
- Creates Multiple Formats: Generates both minified and development versions
- Includes All CSS: Bundles chessground themes, piece images, and widget styles
- Module Wrapping: Converts ES6/CommonJS modules to browser-compatible code
- No External Calls: Everything is self-contained and works offline
npm install # Install all dependencies
npm run build # Create widget production files in dist/
npm run build:builder # Build widget + builder for production
npm run dev # Start demo dev server with live reload
npm run dev:prod # Test production build with dev server
npm run dev:builder # Start builder dev server
npm run typecheck # Run TypeScript type checking- 🔥 Vite Development Server - Lightning-fast live reload
- 📦 TypeScript - Full type safety and IDE support
- 🐛 Source Maps - Easy debugging of original source code
- 🔄 Hot Module Replacement - Instant updates without page refresh
The widget:
- Parses Configuration: Reads data attributes from HTML elements (language, Stockfish settings, etc.)
- Creates Board: Initializes interactive chessground board (bundled)
- Validates Moves: Uses chess.js for legal move validation (bundled)
- Checks Solutions: Supports multiple solution paths with alternative move sequences
- Provides Feedback: Shows visual feedback in the selected language
- AI Counter-Moves: When enabled, wrong moves trigger Stockfish analysis with visual arrows
- Smart Caching: Stores Stockfish responses in localStorage to reduce API calls
Solutions can be provided in two formats:
- UCI notation:
e2e4(from-to squares) - SAN notation:
Nf3(Standard Algebraic Notation)
Examples:
data-solution="e2e4,e7e5,Ng1f3"(UCI)data-solution="e4,e5,Nf3"(SAN)
Separate multiple valid solution paths with the pipe character (|):
data-solution="e2e4,e7e5,Nf3|e2e4,e7e5,Bc4"This allows the puzzle to accept either Nf3 OR Bc4 as the third move. Perfect for tactical puzzles with multiple winning continuations!
The premove feature allows puzzles to start with an opponent's move being played automatically. This is perfect for Lichess puzzle imports where puzzles typically start after the opponent's last move.
How it works:
- Set
data-premove-enabled="true" - Include the opponent's move as the first move in the solution
- The widget will automatically:
- Play the first move with animation (500ms delay)
- Flip the board to show the player's perspective
- Wait for the player to make their move
- When the puzzle is reset, the premove replays automatically
Example:
<div class="chess-puzzle"
data-fen="r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 1"
data-solution="d2d4,f1e1,e5e4"
data-premove-enabled="true"
data-orientation="black">
<!-- d2d4 plays automatically as white's move -->
<!-- Board shows from black's perspective -->
<!-- Player must respond with Re1 -->
</div>Benefits:
- ✅ Matches Lichess puzzle experience exactly
- ✅ Clear visual feedback showing opponent's setup move
- ✅ Automatic board orientation for better UX
- ✅ Works seamlessly with reset functionality
The widget supports multiple languages. Currently available:
- English (default):
data-lang="en" - German:
data-lang="de"
All feedback messages, button labels, and status text will be displayed in the selected language. The system is extensible - additional languages can be added by extending the translation files.
When data-stockfish-enabled="true" is set, wrong moves trigger an AI analysis:
- User makes an incorrect move
- Widget shows "loading" indicator
- Stockfish API analyzes the position (or retrieves from cache)
- Best counter-move is displayed with:
- Animation: Piece moves on the board
- Red Arrow: Visual indicator showing the move direction
- Status Message: Shows the move in UCI notation
- After 2 seconds, both moves are undone
- User can try again
Caching: Stockfish responses are cached in localStorage for 30 days, dramatically reducing API calls and improving performance. The cache is puzzle-specific and automatically manages storage quota.
Configuration Tips:
- Depth 8-10: Fast responses, good for beginners
- Depth 12-15: Balanced (default: 12)
- Depth 16-20: Strongest analysis, slower responses
- Chrome 60+
- Firefox 55+
- Safari 12+
- Edge 79+
The widget bundles these libraries internally:
- chessground v9.2.1 - Chess board UI
- chess.js v1.4.0 - Chess logic and validation
All dependencies are bundled during the build process - no external CDN calls are made.
Both the demo and builder are ready for GitHub Pages deployment!
- Go to Settings → Pages in your GitHub repository
- Set source: Deploy from a branch
- Select branch: main and folder: /demo
- Your demo will be live at:
https://username.github.io/repo-name/
- First, build the builder:
npm run build:builder - Go to Settings → Pages in your GitHub repository
- Set source: Deploy from a branch
- Select branch: main and folder: /dist/builder
- Your builder will be live at:
https://username.github.io/repo-name/
See builder deployment guide →
Note:
- Demo deploys from
/demodirectory (production-ready by default) - Builder deploys from
/dist/builderdirectory (requiresnpm run build:builderfirst) - You can deploy both by creating separate GitHub Pages sites or separate repositories
MIT License - feel free to use in personal and commercial projects.
- Fork the repository
- Create your feature branch
- Make your changes
- Test with
npm run dev - Build with
npm run build - Submit a pull request
Made with ♟️ by chess enthusiasts, for chess enthusiasts.