npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@og-nav/expo-chessboard

v0.1.0

Published

Animated, customizable chessboard for React Native + Expo

Readme

@og-nav/expo-chessboard

Animated, customizable chessboard for React Native + Expo. Single-gesture drag-and-drop, smooth piece reconciliation across moves (including castling, en passant, and promotion), premoves, move-history scrubbing, and a deep customization surface — all built on Reanimated, Gesture Handler, and chess.ts.

import { Chessboard } from "@og-nav/expo-chessboard";

<Chessboard fen="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" boardSize={360} />

Install

npm install @og-nav/expo-chessboard
# or
pnpm add @og-nav/expo-chessboard
# or
yarn add @og-nav/expo-chessboard

Then install the peer dependencies in your Expo app:

npx expo install \
  react-native-reanimated \
  react-native-gesture-handler \
  react-native-svg \
  expo-audio \
  expo-haptics \
  chess.ts

The library does not bundle any of those — they are peer deps so your app and the library share a single instance of each. Reanimated v3+, Gesture Handler v2+, and react-native-svg v15+ are required.

If your app does not already use Reanimated, follow the Reanimated setup guide — you need the babel plugin (react-native-worklets/plugin for Reanimated v4, react-native-reanimated/plugin for v3) and <GestureHandlerRootView> at the root of your app.

Quickstart

Uncontrolled mode (board owns the game)

The simplest way to drop a board into a screen. The board owns its own internal Chess instance and the consumer just listens for moves.

import { Chessboard, type Move } from "@og-nav/expo-chessboard";

export function PlayScreen() {
  return (
    <Chessboard
      boardSize={360}
      onMove={(move: Move) => {
        console.log("played", move.san);
      }}
    />
  );
}

Pass fen to start from a non-standard position. Changing the fen prop later resets the internal board to the new position.

Controlled mode (you own the Chess instance)

For analysis tools, engine integration, or anything that needs to share a Chess instance across the board and other UI.

import { Chessboard, type ChessboardRef } from "@og-nav/expo-chessboard";
import { Chess } from "chess.ts";
import { useMemo, useRef } from "react";

export function AnalysisScreen() {
  const chess = useMemo(() => new Chess(), []);
  const boardRef = useRef<ChessboardRef>(null);

  return (
    <Chessboard
      ref={boardRef}
      chess={chess}
      boardSize={360}
      onMove={(move) => {
        // chess has already been updated by the board
        console.log(chess.fen());
      }}
    />
  );
}

In controlled mode, mutating chess from outside the board (e.g. an engine response, undoing through your own UI) requires a manual boardRef.current?.syncFromChess() so the board picks up the new position.

Programmatic moves

boardRef.current?.animateMove("e2", "e4");
boardRef.current?.animateMove("e7", "e8", "q"); // promotion

animateMove plays through the same animation + reconciliation path that user gestures take, so it handles castling, en passant, and promotion correctly.

Themes

Four named palettes ship with the package. They are plain BoardColors constants you pass to the existing colors prop.

import { Chessboard, THEME_WOOD, THEME_BLUE, THEME_GREEN } from "@og-nav/expo-chessboard";

<Chessboard boardSize={360} colors={THEME_WOOD} />

THEME_DEFAULT, THEME_WOOD, THEME_BLUE, THEME_GREEN are exported. Pass a Partial<BoardColors> to override individual colors of any theme.

Custom pieces

Two ways to override the bundled PNG piece set:

// 1. Per-piece image overrides — anything you don't list falls through
// to the default PNG.
<Chessboard
  boardSize={360}
  pieces={{
    wk: require("./assets/my-white-king.png"),
    bk: require("./assets/my-black-king.png"),
  }}
/>

// 2. Full custom renderer — return any React element. Useful for
// SVGs, unicode glyphs, or animated piece art.
import { Text } from "react-native";
import type { PieceType } from "@og-nav/expo-chessboard";

const UNICODE: Record<PieceType, string> = {
  wk: "♔", wq: "♕", wr: "♖", wb: "♗", wn: "♘", wp: "♙",
  bk: "♚", bq: "♛", br: "♜", bb: "♝", bn: "♞", bp: "♟",
};

<Chessboard
  boardSize={360}
  renderPiece={(piece, size) => (
    <Text style={{ fontSize: size * 0.78, textAlign: "center", width: size }}>
      {UNICODE[piece]}
    </Text>
  )}
/>

External highlights and arrows

Both layers are read-only overlays — they ignore pointer events so they never block the gesture layer.

<Chessboard
  boardSize={360}
  highlightedSquares={[
    { square: "e4", type: "ring" },
    { square: "d5", type: "fill", color: "rgba(0, 200, 0, 0.4)" },
  ]}
  arrows={[
    { from: "e2", to: "e4" },
    { from: "g1", to: "f3", color: "rgba(255, 100, 0, 0.85)" },
  ]}
/>

Both arrays are recomputed declaratively from props on every render — just pass the arrows and highlights you want and the board mirrors them. Arrows respect board flips automatically.

Premoves

<Chessboard
  boardSize={360}
  fen={currentFen}
  playerSide="white"
  premovesEnabled
/>

When it is not the player's turn, dragging a piece queues a premove instead of rejecting the gesture. The queued move shows as a red ring on the from-square and a red arrow to the destination. As soon as the opponent's move arrives (via the controlled chess prop, the fen prop in uncontrolled mode, or animateMove), the board checks whether the premove is now legal. If so, it auto-applies it through the normal animation path; if not, it clears silently.

Tap anywhere to cancel a queued premove, or call boardRef.current?.cancelPremove() programmatically.

Premoves require playerSide to be "white" or "black" — they are a no-op in analysis mode (playerSide="both").

Move-history scrubbing

undo() / redo() step through chess.ts history with the same animation that forward moves use. The board maintains its own redo stack on top of chess.ts so redo() can replay an undone move.

const ref = useRef<ChessboardRef>(null);

<Pressable onPress={() => ref.current?.undo()}><Text>Undo</Text></Pressable>
<Pressable onPress={() => ref.current?.redo()}><Text>Redo</Text></Pressable>

// Jump straight to a specific ply (snaps without animating intermediate
// moves):
ref.current?.goToMoveIndex(0); // back to starting position
ref.current?.goToMoveIndex(ref.current.getMoveIndex()); // back to head

Making any new move while at an index below the head clears the redo stack — that's the "you've created a new branch" event. v0.2 will turn that into a tree node instead of an erase.

Props

| Prop | Type | Default | Description | |---|---|---|---| | boardSize | number | required | Pixel size of one side of the board. | | chess | Chess | — | Controlled mode. Board reads/writes this instance. | | fen | string | starting position | Uncontrolled mode. Board owns an internal Chess from this FEN. Mutually exclusive with chess. | | boardOrientation | "white" \| "black" | "white" | Visual orientation only. | | playerSide | "white" \| "black" \| "both" | "both" | Which side the local user can move. | | colors | Partial<BoardColors> | DEFAULT_COLORS | Palette overrides. Pass a theme constant or a partial object. | | pieces | Partial<Record<PieceType, ImageSourcePropType>> | bundled PNGs | Per-piece image overrides. | | renderPiece | (piece, size) => ReactElement \| null | — | Full custom piece renderer. Overrides pieces. | | showCoordinates | boolean | true | Show file/rank labels in the corners. | | coordinateStyle | TextStyle | — | Style overrides for the coordinate labels. | | highlightedSquares | SquareHighlight[] | — | Read-only ring/fill overlays. | | arrows | Arrow[] | — | Read-only SVG arrow overlays. | | gestureEnabled | boolean | true | Disable all interaction. | | animationDuration | number | 200 | Piece-slide duration in ms. | | soundEnabled | boolean | true | Play move/capture/game-over sounds. | | hapticsEnabled | boolean | true | Trigger haptics on moves. | | premovesEnabled | boolean | false | Queue moves on opponent's turn. | | onMove | (move: Move) => void | — | Fires after every successful move (gesture, programmatic, or premove auto-apply). | | onSquarePress | (square: Square) => void | — | Fires on any tap that does not result in a move or selection. |

Imperative ref API

interface ChessboardRef {
  animateMove(from: string, to: string, promotion?: string): void;
  syncFromChess(): void;
  reset(fen?: string): void;
  getFen(): string;
  cancelPremove(): void;

  // History scrubbing
  undo(): void;
  redo(): void;
  goToMoveIndex(n: number): void;
  getMoveIndex(): number;
  getHistory(): Move[];
  canUndo(): boolean;
  canRedo(): boolean;
}

Example app & smoke list

The repo ships an Expo example app at example/ that doubles as a live demo and the manual test pass for the library. Its Smoke tab is a scrollable list of ~40 self-contained cards — one per public feature and one per regression. Each card has its own board

  • chess instance + reset button, and most have inline controls that exercise the imperative ref API. It's the fastest way to see every prop in action and the best place to verify that something works on real hardware.

What the smoke list covers:

  • Every gesture (drag, tap-to-move, drag-and-drop)
  • Every special chess move (castling both sides, en passant, promotion, capture-promotion)
  • Every theme, custom piece set, custom renderPiece, external highlights, arrows
  • Sounds (move / capture / checkmate / stalemate / muted), haptics
  • Premoves (basic queue, cancel, sequential rounds)
  • Move-history scrubbing (undo / redo / goToMoveIndex)
  • Imperative ref reads (getFen, getHistory, canUndo, canRedo)
  • Controlled vs. uncontrolled mode, mid-game fen swap, ref.reset(fen)
  • Mid-game orientation flip, mid-game boardSize change, background → foreground mid-drag

Run it on your phone

The example app uses Expo Go so you don't need Xcode or Android Studio.

  1. Install Expo Go on your iPhone or Android device from the App Store / Play Store.

  2. Clone the repo and install dependencies:

    git clone https://github.com/og-nav/expo-chessboard.git
    cd expo-chessboard
    pnpm install
    pnpm build           # builds the library into dist/
    cd example
    pnpm install
  3. Start the dev server:

    pnpm start
  4. Scan the QR code printed in the terminal with your phone's camera (iOS) or from inside Expo Go (Android). The app loads, the Smoke tab is the second tab in the bottom bar.

The example imports the library via file:.. so any change you make to src/ followed by pnpm build (in the repo root) will be picked up the next time you reload Expo Go — useful if you want to fork the library and try changes against the smoke list.

If you don't want to run the app yourself, the cards in example/app/(tabs)/smoke/index.tsx are also the most complete set of usage examples in the repo — every prop combination and every ref method is demonstrated there in ready-to-copy form.

License

MIT © og-nav. See LICENSE.