react-native-chessboard
A lightweight, simple, and high-performing chessboard for React Native
git clone https://github.com/enzomanuelmangano/react-native-chessboard.gitenzomanuelmangano/react-native-chessboardReact Native Chessboard
A high-performing, zero-render chessboard for React Native built with Skia and Reanimated.
Features
- Zero React re-renders - All animations and updates happen via shared values
- 60fps gesture performance - Smooth drag and drop with react-native-gesture-handler
- Skia rendering - Hardware-accelerated graphics with @shopify/react-native-skia
- Full chess support - Castling, en passant, pawn promotion
- Programmatic control - Move pieces, undo, reset, and highlight via ref API
Installation
Required peer dependencies:
- react-native-reanimated (>= 4.0.0)
- react-native-gesture-handler (>= 2.14.0)
- @shopify/react-native-skia (>= 2.0.0)
- react-native-worklets (>= 0.7.0)
Requires React Native 0.79+ (New Architecture) and React 19 — the floors set by Skia 2 and Reanimated 4.
bun add react-native-chessboard
or with npm:
npm install react-native-chessboard
Usage
The board uses react-native-gesture-handler, so the app (or at least the subtree containing the board) must be wrapped in GestureHandlerRootView:
import Chessboard from 'react-native-chessboard';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
const App = () => (
<GestureHandlerRootView style={{ flex: 1 }}>
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Chessboard />
</View>
</GestureHandlerRootView>
);
Properties
gestureEnabled?: boolean
Enables gestures for chess pieces.
Default: true
fen?: string
Indicates the initial FEN position of the chessboard. The board rebuilds when this prop changes.
flipped?: boolean
Renders the board from Black's perspective — pieces, coordinate labels, and gestures all follow the orientation.
Default: false
<Chessboard flipped />
withLetters?: boolean
Shows the letters on the bottom horizontal axis of the chessboard.
Default: true
withNumbers?: boolean
Shows the numbers on the left vertical axis of the chessboard.
Default: true
fontSource?: ImageSourcePropType
Optional font asset for the letter and number labels (e.g. require('./Inter.ttf')). Falls back to the platform system font when omitted.
<Chessboard fontSource={require('./assets/Inter.ttf')} />
boardSize?: number
Indicates the chessboard width and height.
Default: Math.floor(SCREEN_WIDTH / 8) * 8
onMove?: (info: MoveResult) => void;
Callback executed after a move is made.
import Chessboard from 'react-native-chessboard';
const App = () => (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Chessboard
onMove={({ state }) => {
if (state.isCheckmate) {
console.log('Checkmate!');
}
}}
/>
</View>
);
The callback receives { move, state } — move is the verbose chess.js Move that was just played (from, to, san, color, captured, …).
The state object contains:
isCheck: booleanisCheckmate: booleanisDraw: booleanisStalemate: booleanisThreefoldRepetition: booleanisInsufficientMaterial: booleanisGameOver: booleanisPromotion: booleanfen: stringhistory: ReadonlyArray<Move>— verbose move history (each entry includesfrom,to,san,color,promotion, captured piece, etc.). Useful for replay, move-list UIs, and analysis.
colors?: ChessboardColorsType
Customize the default colors used in the chessboard.
Default:
- black:
'#62B1A8' - white:
'#D9FDF8' - lastMoveHighlight:
'rgba(255,255,0, 0.5)' - checkmateHighlight:
'#E84855' - promotionPieceButton:
'#FF9B71'
onIllegalMove?: (from: Square, to: Square) => void
Callback executed when a gesture attempts an illegal move (the piece snaps back).
renderEffect?: (params: EffectParams) => React.ReactNode
Render a custom overlay (e.g. a Skia shader) driven by game events. params exposes shared values — centerX/centerY (the relevant king's position), progress (0 → 1 when the effect triggers), trigger ('check' | 'checkmate' | 'stalemate' | '') — plus boardSize.
spriteSource?: ImageSourcePropType
Override the bundled piece sprite sheet with your own. Falls back to the default sheet when omitted.
<Chessboard spriteSource={require('./assets/my-pieces.png')} />
The sheet must follow the standard layout the library expects:
- 6×2 grid (12 cells total)
- Each cell is 128×128 pixels
- Row 0: white pieces in order
p, n, b, r, q, k - Row 1: black pieces in same order
col: 0(p) 1(n) 2(b) 3(r) 4(q) 5(k)
row 0: wp wn wb wr wq wk ← white
row 1: bp bn bb br bq bk ← black
total: 768 × 256
Any ImageSourcePropType is accepted: require(...), { uri }, etc.
Generating a sheet from individual piece images
If you only have 12 individual PNGs (one per piece) and need to compose them into a single sheet, the library ships a CLI for that:
npx react-native-chessboard-generate-sprite \ --input ./my-pieces \ --output ./assets/my-sprite.png
The input directory must contain these files:
wp.png wn.png wb.png wr.png wq.png wk.png
bp.png bn.png bb.png br.png bq.png bk.png
Options:
--input <dir>— directory holding the 12 PNGs--output <path>— output sheet path--cell-size=<n>— cell size in pixels (default128)
The script depends on sharp; install it first if you don't already have it:
npm install --save-dev sharp
Then point spriteSource at the generated file:
<Chessboard spriteSource={require('./assets/my-sprite.png')} />
Ref API
The chessboard exposes a ref for programmatic control:
import Chessboard, { ChessboardRef } from 'react-native-chessboard';
const App = () => {
const chessboardRef = useRef<ChessboardRef>(null);
useEffect(() => {
(async () => {
await chessboardRef.current?.move({ from: 'e2', to: 'e4' });
await chessboardRef.current?.move({ from: 'e7', to: 'e5' });
await chessboardRef.current?.move({ from: 'g1', to: 'f3' });
})();
}, []);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Chessboard ref={chessboardRef} />
</View>
);
};
move({ from: Square; to: Square; promotion?: PieceSymbol }): Promise<Move | undefined>
Moves a piece programmatically. Pass promotion ('q' | 'r' | 'b' | 'n') to resolve pawn promotions without showing the picker dialog.
The Promise resolves when the piece animation has settled (or immediately with undefined for an invalid move), so awaited sequences play back cleanly:
await ref.current?.move({ from: 'e2', to: 'e4' }); // resolves after the piece lands
undo(): Move | null
Undoes the last move. Returns the undone move or null if no moves to undo.
highlight({ square: Square; color?: string }): void
Highlights a square. Default color is 'rgba(255,255,0, 0.5)'.
resetAllHighlightedSquares(): void
Clears all highlighted squares.
resetBoard(fen?: string, opts?): Promise<void>
Resets the board. Optionally loads a new FEN position.
Resolves once a slide animation has settled — or was cancelled by a newer
interaction. Without slide there is nothing to animate and the returned
promise is already resolved, so awaiting is always optional.
opts enables animated history navigation when stepping between positions:
slide?: { from: Square; to: Square }— animates the piece that ends ontosliding in fromfrom, instead of snapping the whole boardlastMove?: { from: Square; to: Square } | null— squares to highlight as the move that produced this position
// step forward through a stored game
await ref.current?.resetBoard(nextFen, {
slide: { from: move.from, to: move.to },
lastMove: { from: move.from, to: move.to },
});
// the slide has landed — safe to start the next one without it being cancelled
getState(): ChessboardState
Returns the current state of the chessboard.
Preloading the sprite sheet
Piece sprites are decoded asynchronously, so the very first board of a session can paint an empty checkerboard for a frame or two before its pieces appear. Decoded sheets are cached for the lifetime of the app, so this only ever affects the first board — but you can remove it entirely by decoding the sheet up front, typically alongside font loading during splash:
import { preloadPieceSpriteSheet } from 'react-native-chessboard';
await preloadPieceSpriteSheet();
Pass a source to preload a custom sheet — use the same value you give
spriteSource:
await preloadPieceSpriteSheet(require('./assets/my-pieces.png'));
Resolves to the decoded SkImage, or null if the source cannot be resolved
or decoded. Calling it more than once is free — later calls hit the cache.
When a board mounts without a preloaded sheet, its pieces fade in over 180ms
rather than popping in.
Migration from v1.x
Breaking Changes
-
New peer dependencies:
@shopify/react-native-skia >= 2.0.0,react-native-worklets >= 0.7.0 -
Minimum versions: React Native 0.79+ (New Architecture), React 19, Reanimated 4+
-
State API changes (chess.js v1.0):
in_check→isCheckin_checkmate→isCheckmatein_draw→isDrawin_stalemate→isStalematein_threefold_repetition→isThreefoldRepetitioninsufficient_material→isInsufficientMaterialgame_over→isGameOverin_promotion→isPromotion
-
renderPieceprop removed: Per-piece JSX rendering is no longer supported in v2.0. UsespriteSourceto swap the entire piece set instead (see above).
Migration Steps
- Install the new peer dependency:
bun add @shopify/react-native-skia
- Update your
onMovecallbacks to use the new state property names:
// Before (v1.x)
onMove={({ state }) => {
if (state.in_checkmate) { /* ... */ }
}}
// After (v2.0)
onMove={({ state }) => {
if (state.isCheckmate) { /* ... */ }
}}
Contributing
See the contributing guide to learn how to contribute to the repository and the development workflow.
License
MIT
more like this
cc65-Chess
Portable chess game in C. Commodore 64, Apple 2, Atari, Oric, Commander X16, curses terminal, etc.