dorkhub

react-native-chessboard

A lightweight, simple, and high-performing chessboard for React Native

enzomanuelmangano
TypeScript10435 forksMITupdated 6 days ago
git clone https://github.com/enzomanuelmangano/react-native-chessboard.gitenzomanuelmangano/react-native-chessboard

React 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:

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: boolean
  • isCheckmate: boolean
  • isDraw: boolean
  • isStalemate: boolean
  • isThreefoldRepetition: boolean
  • isInsufficientMaterial: boolean
  • isGameOver: boolean
  • isPromotion: boolean
  • fen: string
  • history: ReadonlyArray<Move> — verbose move history (each entry includes from, 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 (default 128)

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 on to sliding in from from, instead of snapping the whole board
  • lastMove?: { 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

  1. New peer dependencies: @shopify/react-native-skia >= 2.0.0, react-native-worklets >= 0.7.0

  2. Minimum versions: React Native 0.79+ (New Architecture), React 19, Reanimated 4+

  3. State API changes (chess.js v1.0):

    • in_checkisCheck
    • in_checkmateisCheckmate
    • in_drawisDraw
    • in_stalemateisStalemate
    • in_threefold_repetitionisThreefoldRepetition
    • insufficient_materialisInsufficientMaterial
    • game_overisGameOver
    • in_promotionisPromotion
  4. renderPiece prop removed: Per-piece JSX rendering is no longer supported in v2.0. Use spriteSource to swap the entire piece set instead (see above).

Migration Steps

  1. Install the new peer dependency:
bun add @shopify/react-native-skia
  1. Update your onMove callbacks 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.

C50

search

search projects, people, and tags