@react-chess-tools/react-chess-puzzle
A lightweight, customizable React component library for rendering and interacting with chess puzzles
Table of Contents
- Overview
- Features
- Installation
- Quick Start
- Demo
- Puzzle Solving Flow
- API Reference
- Hooks
- Integration with react-chess-game
- Examples
- License
Overview
@react-chess-tools/react-chess-puzzle is a React component library for creating interactive chess puzzle experiences. Built on top of @react-chess-tools/react-chess-game, it provides puzzle-specific features like move validation, hints, and progress tracking.
Features
- Move Validation - Automatically validates moves against the puzzle solution
- Hints - Show the next correct move to help users
- Progress Tracking - Track puzzle state (not-started, in-progress, solved, failed)
- Callbacks - React to puzzle solve/fail events
- Built-in Reset - Easily restart puzzles or load new ones
- Sound Effects - Integrates with ChessGame.Sounds for audio feedback
- Keyboard Controls - Navigate through puzzle moves with keyboard
- TypeScript - Full TypeScript support with comprehensive type definitions
- Multiple solutions - Accept any checkmate as a solution (configurable via
solveOnCheckmate)
Styling
All components accept standard HTML attributes (className, style, id, data-*, aria-*), making them compatible with any CSS approach:
Tailwind CSS
const puzzle = {
fen: "r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3",
moves: ["d2d4", "e5d4", "f3d4"],
makeFirstMove: false,
};
<ChessPuzzle.Root puzzle={puzzle}>
<ChessPuzzle.Board className="rounded-lg shadow-lg" />
<div className="flex gap-2 mt-4">
<ChessPuzzle.Reset className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
Try Again
</ChessPuzzle.Reset>
<ChessPuzzle.Hint className="px-4 py-2 bg-gray-200 rounded hover:bg-gray-300">
Show Hint
</ChessPuzzle.Hint>
</div>
</ChessPuzzle.Root>;
CSS Modules
import styles from "./Puzzle.module.css";
<ChessPuzzle.Board className={styles.board} />;
Custom Theme
See the ChessPuzzle.Root props for the theme prop to customize puzzle-specific colors (success, failure, hint).
Installation
npm install @react-chess-tools/react-chess-game @react-chess-tools/react-chess-puzzle
yarn add @react-chess-tools/react-chess-game @react-chess-tools/react-chess-puzzle
pnpm add @react-chess-tools/react-chess-game @react-chess-tools/react-chess-puzzle
Quick Start
import { ChessPuzzle } from "@react-chess-tools/react-chess-puzzle";
function App() {
const puzzle = {
fen: "r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3",
moves: ["d2d4", "e5d4", "f3d4"],
makeFirstMove: false,
};
return (
<ChessPuzzle.Root puzzle={puzzle}>
<ChessPuzzle.Board />
<ChessPuzzle.Reset>Restart</ChessPuzzle.Reset>
<ChessPuzzle.Hint>Get Hint</ChessPuzzle.Hint>
</ChessPuzzle.Root>
);
}
Demo
Visit the live demo to see the component in action.
Puzzle Solving Flow
- Initial Setup - The board displays the position from the FEN string
- First Move - If
makeFirstMoveistrue, the component automatically plays the first move - User Interaction - The user attempts to solve the puzzle by making moves
- Validation - Each move is validated against the solution:
- Correct move: The puzzle continues, opponent's response is auto-played
- Incorrect move: The puzzle is marked as failed
- Completion - When all correct moves are made, the puzzle is marked as solved
API Reference
ChessPuzzle.Root
The root component that provides puzzle context to all child components.
Note: This is a logic-only component (Context Provider). It does not render any DOM elements.
Props
| Name | Type | Default | Description |
|---|---|---|---|
puzzle |
Puzzle |
- | The puzzle configuration (required) |
onSolve |
(ctx: ChessPuzzleContextType) => void |
- | Callback when puzzle is solved |
onFail |
(ctx: ChessPuzzleContextType) => void |
- | Callback when an incorrect move is made |
solveOnCheckmate |
boolean |
true |
When true, any checkmate move solves the puzzle |
theme |
PartialChessPuzzleTheme |
- | Optional theme configuration |
children |
ReactNode |
- | Child components |
Puzzle Object
| Property | Type | Default | Description |
|---|---|---|---|
fen |
string |
- | Initial position in FEN notation |
moves |
string[] |
- | Solution moves in algebraic or UCI notation |
makeFirstMove |
boolean |
false |
Whether to auto-play the first move |
Example
<ChessPuzzle.Root
puzzle={{
fen: "4kb1r/p2r1ppp/4qn2/1B2p1B1/4P3/1Q6/PPP2PPP/2KR4 w k - 0 1",
moves: ["Bxd7+", "Nxd7", "Qb8+", "Nxb8", "Rd8#"],
makeFirstMove: false,
}}
onSolve={(ctx) => console.log("Solved!", ctx.movesPlayed)}
onFail={(ctx) => console.log("Failed at move", ctx.movesPlayed)}
>
<ChessPuzzle.Board />
</ChessPuzzle.Root>
ChessPuzzle.Board
Renders the chess board. Delegates to ChessGame.Board under the hood.
Supports ref forwarding and all standard HTML div attributes (className, style, id, data-, aria-, etc.).
Props
| Name | Type | Description |
|---|---|---|
options |
ChessboardOptions |
Options forwarded to react-chessboard v5 |
ref |
Ref<HTMLDivElement> |
Forwarded ref to the underlying board element |
className |
string |
Custom CSS class names |
style |
CSSProperties |
Custom inline styles |
... |
HTMLAttributes<HTMLDivElement> |
All standard HTML div attributes |
Example
<ChessPuzzle.Root puzzle={puzzle}>
<ChessPuzzle.Board
options={{
showNotation: true,
animationDurationInMs: 200,
}}
className="puzzle-board"
style={{ boxShadow: "0 4px 6px rgba(0,0,0,0.1)" }}
/>
</ChessPuzzle.Root>
ChessPuzzle.Reset
A button component that resets the current puzzle or loads a new one.
Supports ref forwarding, asChild pattern, and all standard HTML button attributes (className, style, disabled, etc.).
Props
| Name | Type | Default | Description |
|---|---|---|---|
puzzle |
Puzzle |
- | New puzzle to load (resets current if not provided) |
onReset |
(ctx: ChessPuzzleContextType) => void |
- | Callback after reset |
showOn |
Status[] |
["failed", "solved"] |
States in which the button is visible |
asChild |
boolean |
false |
Render as child element (slot pattern) |
ref |
Ref<HTMLButtonElement> |
Forwarded ref to the button element | |
className |
string |
Custom CSS class names | |
... |
ButtonHTMLAttributes<HTMLButtonElement> |
All standard HTML button attributes |
Status values: "not-started", "in-progress", "solved", "failed"
Example
<ChessPuzzle.Root puzzle={puzzle}>
<ChessPuzzle.Board />
<ChessPuzzle.Reset>Try Again</ChessPuzzle.Reset>
<ChessPuzzle.Reset puzzle={nextPuzzle}>Next Puzzle</ChessPuzzle.Reset>
</ChessPuzzle.Root>
Using asChild Pattern
// Render as a custom button component
import { MyCustomButton } from './MyButton';
<ChessPuzzle.Root puzzle={puzzle}>
<ChessPuzzle.Board />
<ChessPuzzle.Reset asChild>
<MyCustomButton variant="primary">Try Again</MyCustomButton>
</ChessPuzzle.Reset>
</ChessPuzzle.Root>
// Render as a link
<ChessPuzzle.Root puzzle={puzzle}>
<ChessPuzzle.Board />
<ChessPuzzle.Reset asChild>
<a href="#" onClick={(e) => e.preventDefault()}>
Restart Puzzle
</a>
</ChessPuzzle.Reset>
</ChessPuzzle.Root>
Event Handler Composition
When using asChild, both your component's onClick and the Reset's onClick handler will work together:
<ChessPuzzle.Reset asChild>
<button onClick={() => console.log("Custom handler")} className="custom-btn">
Try Again
</button>
</ChessPuzzle.Reset>
// Both custom handler and reset logic will execute
ChessPuzzle.Hint
A button that highlights the next correct move on the board.
Supports ref forwarding, asChild pattern, and all standard HTML button attributes (className, style, disabled, etc.).
Props
| Name | Type | Default | Description |
|---|---|---|---|
showOn |
Status[] |
["not-started", "in-progress"] |
States in which the button is visible |
asChild |
boolean |
false |
Render as child element (slot pattern) |
ref |
Ref<HTMLButtonElement> |
Forwarded ref to the button element | |
className |
string |
Custom CSS class names | |
... |
ButtonHTMLAttributes<HTMLButtonElement> |
All standard HTML button attributes |
Example
const puzzle = {
fen: "r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3",
moves: ["d2d4", "e5d4", "f3d4"],
makeFirstMove: false,
};
<ChessPuzzle.Root puzzle={puzzle}>
<ChessPuzzle.Board />
<ChessPuzzle.Hint>Show Hint</ChessPuzzle.Hint>
</ChessPuzzle.Root>;
Using asChild Pattern
<ChessPuzzle.Root puzzle={puzzle}>
<ChessPuzzle.Board />
<ChessPuzzle.Hint asChild>
<button className="hint-btn">💡 Show Hint</button>
</ChessPuzzle.Hint>
</ChessPuzzle.Root>
Hooks
useChessPuzzleContext
Access the puzzle state and methods from any child component.
import { useChessPuzzleContext } from "@react-chess-tools/react-chess-puzzle";
function PuzzleStatus() {
const { puzzleState, movesPlayed, totalMoves, resetPuzzle, onHint } =
useChessPuzzleContext();
return (
<div>
<p>Status: {puzzleState}</p>
<p>
Progress: {movesPlayed}/{totalMoves} moves
</p>
<button onClick={resetPuzzle}>Reset</button>
<button onClick={onHint}>Hint</button>
</div>
);
}
Return Values
| Property | Type | Description |
|---|---|---|
status |
Status |
Current puzzle state |
puzzleState |
Status |
Alias for status |
movesPlayed |
number |
Number of correct moves made |
totalMoves |
number |
Total moves in the solution |
puzzle |
Puzzle |
The current puzzle object |
hint |
Hint |
Current hint state |
nextMove |
string | null |
The next correct move |
isPlayerTurn |
boolean |
Whether it's the player's turn to move |
changePuzzle |
(puzzle: Puzzle) => void |
Load a new puzzle |
resetPuzzle |
() => void |
Reset the current puzzle |
onHint |
() => void |
Show hint for next move |
useChessGameContext
Since react-chess-puzzle is built on react-chess-game, you can also access the underlying game context.
import { useChessGameContext } from "@react-chess-tools/react-chess-game";
function BoardInfo() {
const { currentFen, info, methods } = useChessGameContext();
return (
<div>
<p>Turn: {info.turn === "w" ? "White" : "Black"}</p>
<button onClick={() => methods.flipBoard()}>Flip Board</button>
</div>
);
}
Integration with react-chess-game
Since react-chess-puzzle is built on react-chess-game, you can use any of its components:
Adding Sound Effects
import { ChessPuzzle } from "@react-chess-tools/react-chess-puzzle";
import { ChessGame } from "@react-chess-tools/react-chess-game";
function PuzzleWithSounds() {
const puzzle = {
fen: "r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3",
moves: ["d2d4", "e5d4", "f3d4"],
};
return (
<ChessPuzzle.Root puzzle={puzzle}>
<ChessGame.Sounds />
<ChessPuzzle.Board />
</ChessPuzzle.Root>
);
}
Adding Keyboard Controls
import { ChessPuzzle } from "@react-chess-tools/react-chess-puzzle";
import { ChessGame } from "@react-chess-tools/react-chess-game";
function PuzzleWithKeyboard() {
const puzzle = {
fen: "r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3",
moves: ["d2d4", "e5d4", "f3d4"],
};
return (
<ChessPuzzle.Root puzzle={puzzle}>
<ChessGame.KeyboardControls />
<ChessPuzzle.Board />
</ChessPuzzle.Root>
);
}
Examples
Basic Puzzle
import { ChessPuzzle } from "@react-chess-tools/react-chess-puzzle";
function BasicPuzzle() {
const puzzle = {
fen: "r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3",
moves: ["d2d4", "e5d4", "f3d4"],
makeFirstMove: false,
};
return (
<ChessPuzzle.Root puzzle={puzzle}>
<ChessPuzzle.Board />
<ChessPuzzle.Reset>Restart</ChessPuzzle.Reset>
<ChessPuzzle.Hint>Hint</ChessPuzzle.Hint>
</ChessPuzzle.Root>
);
}
Puzzle with Callbacks
import {
ChessPuzzle,
type ChessPuzzleContextType,
} from "@react-chess-tools/react-chess-puzzle";
import { useState } from "react";
function PuzzleWithScore() {
const [score, setScore] = useState(0);
const handleSolve = (ctx: ChessPuzzleContextType) => {
setScore((prev) => prev + 10);
console.log(`Solved in ${ctx.movesPlayed} moves!`);
};
const handleFail = (ctx: ChessPuzzleContextType) => {
setScore((prev) => Math.max(0, prev - 5));
console.log("Incorrect move!");
};
return (
<div>
<p>Score: {score}</p>
<ChessPuzzle.Root
puzzle={puzzle}
onSolve={handleSolve}
onFail={handleFail}
>
<ChessPuzzle.Board />
<ChessPuzzle.Reset>Try Again</ChessPuzzle.Reset>
</ChessPuzzle.Root>
</div>
);
}
Puzzle Trainer with Multiple Puzzles
import {
ChessPuzzle,
type ChessPuzzleContextType,
} from "@react-chess-tools/react-chess-puzzle";
import { ChessGame } from "@react-chess-tools/react-chess-game";
import { useState } from "react";
const puzzles = [
{
fen: "4kb1r/p2r1ppp/4qn2/1B2p1B1/4P3/1Q6/PPP2PPP/2KR4 w k - 0 1",
moves: ["Bxd7+", "Nxd7", "Qb8+", "Nxb8", "Rd8#"],
makeFirstMove: false,
},
{
fen: "6k1/5p1p/p1q1p1p1/1pB1P3/1Pr3Pn/P4P1P/4Q3/3R2K1 b - - 0 31",
moves: ["h4f3", "e2f3", "c4c5", "d1d8", "g8g7", "f3f6"],
makeFirstMove: true,
},
];
function PuzzleTrainer() {
const [currentIndex, setCurrentIndex] = useState(0);
const [score, setScore] = useState(0);
const nextPuzzle = () => {
setCurrentIndex((prev) => (prev + 1) % puzzles.length);
};
const handleSolve = (ctx: ChessPuzzleContextType) => {
setScore((prev) => prev + 10);
nextPuzzle();
};
const handleFail = () => {
setScore((prev) => Math.max(0, prev - 5));
nextPuzzle();
};
return (
<div className="puzzle-trainer">
<div className="score">Score: {score}</div>
<ChessPuzzle.Root
puzzle={puzzles[currentIndex]}
onSolve={handleSolve}
onFail={handleFail}
>
<ChessGame.Sounds />
<ChessGame.KeyboardControls />
<ChessPuzzle.Board />
<div className="controls">
<ChessPuzzle.Reset>Restart</ChessPuzzle.Reset>
<ChessPuzzle.Hint>Hint</ChessPuzzle.Hint>
<ChessPuzzle.Reset
puzzle={puzzles[(currentIndex + 1) % puzzles.length]}
>
Skip
</ChessPuzzle.Reset>
</div>
</ChessPuzzle.Root>
</div>
);
}
Custom Status Display
import {
ChessPuzzle,
useChessPuzzleContext,
} from "@react-chess-tools/react-chess-puzzle";
function PuzzleStatusDisplay() {
const { puzzleState, movesPlayed, totalMoves } = useChessPuzzleContext();
const messages = {
"not-started": "Make your move to start",
"in-progress": `Progress: ${movesPlayed}/${totalMoves} moves`,
solved: "Puzzle solved! Well done!",
failed: "Incorrect move. Try again!",
};
return <div className={`status ${puzzleState}`}>{messages[puzzleState]}</div>;
}
function ResetLabel() {
const { puzzleState } = useChessPuzzleContext();
return puzzleState === "solved" ? "Next Puzzle" : "Try Again";
}
function PuzzleWithStatus() {
const puzzle = {
fen: "r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3",
moves: ["d2d4", "e5d4", "f3d4"],
};
return (
<ChessPuzzle.Root puzzle={puzzle}>
<PuzzleStatusDisplay />
<ChessPuzzle.Board />
<ChessPuzzle.Reset showOn={["solved", "failed"]}>
<ResetLabel />
</ChessPuzzle.Reset>
</ChessPuzzle.Root>
);
}
License
This project is MIT licensed.
Show Your Support
Give a star if this project helped you!