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

chessiro-canvas

v0.1.31

Published

Lightweight, high-performance React chessboard component

Downloads

1,133

Readme

chessiro-canvas

Lightweight React chessboard with low overhead interaction primitives inspired by chessground.

Built for https://chessiro.com, but can be used by all

  • Zero runtime dependencies
  • TypeScript-first API
  • Drag, click-move, arrows, marks, premoves, promotion, overlays
  • Built for controlled usage in analysis and coaching apps

Install

npm install chessiro-canvas

Quick Start

Your container must define width, and board height follows width (square board).

import { ChessiroCanvas, INITIAL_FEN } from 'chessiro-canvas';

export default function App() {
  return (
    <div style={{ width: 520 }}>
      <ChessiroCanvas position={INITIAL_FEN} />
    </div>
  );
}

ChessiroCanvas is a controlled component. For playable boards, your onMove must update position after validating the move (typically via chess.js or chessops).

Piece Rendering (Default + Custom)

ChessiroCanvas ships with embedded default SVG pieces and renders them by default with no asset hosting setup.

<ChessiroCanvas position={fen} />

Piece license note:

  • Bundled default piece artwork is generated from react-chessboard defaults (MIT license).
  • You can replace it any time via pieceSet.path.

Use pieceSet.path only when you want to override with your own hosted piece set.

<ChessiroCanvas
  position={fen}
  pieceSet={{
    id: 'alpha',
    name: 'Alpha',
    path: '/pieces/alpha', // expects /pieces/alpha/wp.svg ... /bk.svg
  }}
/>

If pieces appear as broken images, upgrade to the latest package version.

Customize Legal Move UI

Use squareVisuals to customize legal dots, capture rings, premove hints, marks, and check overlay.

<ChessiroCanvas
  position={fen}
  dests={dests}
  squareVisuals={{
    legalDot: 'rgba(30, 144, 255, 0.55)',
    legalDotOutline: 'rgba(255, 255, 255, 0.95)',
    legalCaptureRing: 'rgba(30, 144, 255, 0.8)',
    premoveDot: 'rgba(155, 89, 182, 0.55)',
    premoveCaptureRing: 'rgba(155, 89, 182, 0.75)',
    selectedOutline: 'rgba(255, 255, 255, 1)',
    markOverlay: 'rgba(244, 67, 54, 0.6)',
    markOutline: 'rgba(244, 67, 54, 0.9)',
  }}
/>

Customize Other UI Layers

<ChessiroCanvas
  position={fen}
  showMargin={true}
  marginRadius={16}
  boardRadius={14}
  arrowVisuals={{
    lineWidth: 0.2,
    opacity: 1,
    markerWidth: 5,
    markerHeight: 5,
  }}
  notationVisuals={{
    fontFamily: 'JetBrains Mono, monospace',
    fontSize: 11,
    onBoardFontSize: 11,
    opacity: 0.95,
  }}
  promotionVisuals={{
    panelColor: 'rgba(20, 24, 36, 0.98)',
    titleColor: '#f2f6ff',
    optionBackground: 'rgba(255, 255, 255, 0.08)',
    optionTextColor: '#f2f6ff',
    cancelTextColor: '#cbd5e1',
  }}
  overlayVisuals={{
    background: 'rgba(2, 6, 23, 0.85)',
    color: '#f8fafc',
    borderRadius: '6px',
    fontSize: '11px',
  }}
/>

boardRadius controls inner board corners, and marginRadius controls outer margin corners. Both work independently, so you can style rounded inner + outer frames together. For notation sizing, notationVisuals.fontSize and notationVisuals.onBoardFontSize accept either number or CSS string.

Integration With chess.js

npm install chess.js chessiro-canvas
import { useMemo, useState } from 'react';
import { Chess } from 'chess.js';
import { ChessiroCanvas, type Dests, type Square } from 'chessiro-canvas';

export function ChessJsBoard() {
  const [chess] = useState(() => new Chess());
  const [fen, setFen] = useState(() => chess.fen());

  const dests = useMemo<Dests>(() => {
    const map = new Map<Square, Square[]>();
    const moves = chess.moves({ verbose: true });
    for (const move of moves) {
      const from = move.from as Square;
      const to = move.to as Square;
      const current = map.get(from);
      if (current) current.push(to);
      else map.set(from, [to]);
    }
    return map;
  }, [fen]);

  return (
    <ChessiroCanvas
      position={fen}
      turnColor={chess.turn()}
      movableColor={chess.turn()}
      dests={dests}
      onMove={(from, to, promotion) => {
        const result = chess.move({ from, to, promotion });
        if (!result) return false;
        setFen(chess.fen());
        return true;
      }}
    />
  );
}

Important for chess.js users:

  • chess.js mutates the same Chess instance in place.
  • Do not key useMemo/useEffect off the chess object reference for legal moves, check square, turn state, etc.
  • Key derived UI state from fen (or move history), because fen changes on every accepted move.

Correct dependency pattern:

const [chess] = useState(() => new Chess());
const [fen, setFen] = useState(() => chess.fen());

const dests = useMemo(() => {
  const map = new Map();
  for (const move of chess.moves({ verbose: true })) {
    const list = map.get(move.from) ?? [];
    list.push(move.to);
    map.set(move.from, list);
  }
  return map;
}, [fen]); // <- use fen, not [chess]

Integration With chessops

npm install chessops chessiro-canvas
import { useMemo, useState } from 'react';
import { Chess } from 'chessops/chess';
import { chessgroundDests } from 'chessops/compat';
import { parseFen, makeFen } from 'chessops/fen';
import { parseUci } from 'chessops/util';
import { ChessiroCanvas, INITIAL_GAME_FEN } from 'chessiro-canvas';

export function ChessopsBoard() {
  const [pos, setPos] = useState(() =>
    Chess.fromSetup(parseFen(INITIAL_GAME_FEN).unwrap()).unwrap(),
  );

  const fen = useMemo(() => makeFen(pos.toSetup()), [pos]);
  const dests = useMemo(() => chessgroundDests(pos), [pos]);
  const turn = pos.turn === 'white' ? 'w' : 'b';

  return (
    <ChessiroCanvas
      position={fen}
      turnColor={turn}
      movableColor={turn}
      dests={dests}
      onMove={(from, to, promotion) => {
        const uci = `${from}${to}${promotion ?? ''}`;
        const move = parseUci(uci);
        if (!move || !pos.isLegal(move)) return false;
        const next = pos.clone();
        next.play(move);
        setPos(next);
        return true;
      }}
    />
  );
}

INITIAL_FEN is piece-placement only (UI-friendly). For engine integrations, use INITIAL_GAME_FEN so castling rights are present.

Features

  • FEN-based board rendering
  • Built-in default piece set shipped with the package
  • Click-to-move and drag-to-move
  • Legal move dots and capture rings
  • Premoves with optional external event hooks
  • Right-click arrows and marks
  • Last-move, check, and custom square highlights
  • Move-quality badge support
  • Promotion chooser
  • Text overlays with custom renderer
  • Keyboard callbacks (ArrowLeft, ArrowRight, Home, End, F, X, Escape)
  • Theme, piece set, and custom piece renderer support

Core API

ChessiroCanvas props

| Prop | Type | Default | Notes | | --- | --- | --- | --- | | position | string | start position | FEN (piece placement or full FEN; placement is parsed) | | orientation | 'white' \| 'black' | 'white' | Board orientation | | interactive | boolean | true | Disables move interactions when false | | turnColor | 'w' \| 'b' | undefined | Needed for turn-aware move/premove flow | | movableColor | 'w' \| 'b' \| 'both' | undefined | Restricts which side can move | | onMove | (from, to, promotion?) => boolean | undefined | Return true to accept move | | dests | Map<Square, Square[]> | undefined | Legal destinations per square | | lastMove | { from: string; to: string } \| null | undefined | Last move highlight | | check | string \| null | undefined | King-in-check square | | premovable | PremoveConfig | undefined | Enables premove and callbacks | | arrows | Arrow[] | [] | Controlled arrows | | onArrowsChange | (arrows) => void | undefined | Arrow updates | | markedSquares | string[] | internal | Controlled marks | | onMarkedSquaresChange | (squares) => void | undefined | Mark updates | | arrowBrushes | Partial<ArrowBrushes> | default set | Override arrow colors | | arrowVisuals | Partial<ArrowVisuals> | undefined | Customize arrow width, opacity, marker size, and arrow margin | | snapArrowsToValidMoves | boolean | true | Queen/knight snap behavior | | theme | BoardTheme | built-in theme | Board colors | | pieceSet | PieceSet | bundled default pieces | Optional custom piece asset path config | | pieces | Record<string, () => ReactNode> | undefined | Custom piece renderer map | | showMargin | boolean | true | Margin frame for notation | | marginThickness | number | 24 | Margin px | | marginRadius | number \| string | 4 | Outer margin frame corner radius | | boardRadius | number \| string | 0 | Board corner radius (works with or without margin) | | showNotation | boolean | true | Coordinate labels | | notationVisuals | Partial<NotationVisuals> | undefined | Customize notation font family, size, weight, color, and offsets | | highlightedSquares | Record<string, string> | {} | Arbitrary square background colors | | squareVisuals | Partial<SquareVisuals> | undefined | Customize legal/premove indicators, marks, selected outline, and check overlay | | moveQualityBadge | MoveQualityBadge \| null | undefined | Badge icon on square | | allowDragging | boolean | true | Drag interaction toggle | | allowDrawingArrows | boolean | true | Right-click arrows/marks toggle | | showAnimations | boolean | true | Piece animation toggle | | animationDurationMs | number | 200 | Piece animation length | | blockTouchScroll | boolean | false | Prevent scrolling on touch interaction | | overlays | TextOverlay[] | [] | Text overlays | | overlayRenderer | (overlay) => ReactNode | undefined | Custom overlay renderer | | overlayVisuals | Partial<OverlayVisuals> | undefined | Customize default overlay bubble style (when overlayRenderer is not provided) | | onSquareClick | (square) => void | undefined | Square click callback | | onClearOverlays | () => void | undefined | Called when board clears current ply overlays | | promotionVisuals | Partial<PromotionVisuals> | undefined | Customize promotion dialog backdrop, panel, option buttons, and text colors | | onPrevious onNext onFirst onLast onFlipBoard onShowThreat onDeselect | callbacks | undefined | Keyboard callback hooks | | className | string | undefined | Wrapper class | | style | CSSProperties | undefined | Wrapper style |

Exported helpers

  • INITIAL_FEN
  • INITIAL_GAME_FEN
  • readFen(fen) / writeFen(pieces)
  • premoveDests(square, pieces, color)
  • preloadPieceSet(path)
  • DEFAULT_ARROW_BRUSHES

Examples

Controlled legal moves (dests)

import { useMemo } from 'react';
import { ChessiroCanvas, type Dests, type Square } from 'chessiro-canvas';

function Board({ fen, legalMovesByFrom, onMove }) {
  const dests = useMemo<Dests>(() => {
    const map = new Map<Square, Square[]>();
    for (const [from, toList] of Object.entries(legalMovesByFrom)) {
      map.set(from as Square, toList as Square[]);
    }
    return map;
  }, [legalMovesByFrom]);

  return (
    <div style={{ width: 560 }}>
      <ChessiroCanvas position={fen} dests={dests} onMove={onMove} />
    </div>
  );
}

Controlled arrows and marks

import { useState } from 'react';
import { ChessiroCanvas, type Arrow } from 'chessiro-canvas';

function AnalysisBoard({ fen }: { fen: string }) {
  const [arrows, setArrows] = useState<Arrow[]>([]);
  const [marks, setMarks] = useState<string[]>([]);

  return (
    <div style={{ width: 560 }}>
      <ChessiroCanvas
        position={fen}
        arrows={arrows}
        onArrowsChange={setArrows}
        markedSquares={marks}
        onMarkedSquaresChange={setMarks}
      />
    </div>
  );
}

Theme and piece assets

<ChessiroCanvas
  position={fen}
  theme={{
    id: 'wood',
    name: 'Wood',
    darkSquare: '#8B5A2B',
    lightSquare: '#F0D9B5',
    margin: '#5C3B1F',
    lastMoveHighlight: '#E7C15D',
    selectedPiece: '#A86634',
  }}
  pieceSet={{
    id: 'alpha',
    name: 'Alpha',
    path: '/pieces/alpha',
  }}
/>

Benchmark vs react-chessboard

Latest benchmark file: benchmarks/latest.json Latest browser benchmark file: benchmarks/browser/latest.json

Run Node benchmark:

npm run benchmark

Run browser benchmark (Playwright + Chromium, local vs origin/main vs react-chessboard):

npm run benchmark:browser

Quick browser benchmark:

npm run benchmark:browser:quick

Method:

  • Environment: Node v25.6.1, macOS arm64, Apple M4 (10 cores), 16 GB RAM
  • 8 measured rounds + 2 warmup rounds
  • 300 position updates per round
  • Position updates replay multiple real move-playthrough scenarios (castling, captures, endgames, promotion)
  • Same board size (640px) and animations disabled for both libraries
  • Metrics: mount wall time, update wall time, React Profiler update duration, bundle gzip
  • Harnesses: scripts/benchmark.mjs (Node) and scripts/benchmark-playwright.mjs (browser)

Run a subset of scenarios:

BENCH_SCENARIOS=italian-castling,sicilian-captures npm run benchmark
BENCH_SCENARIOS=italian-castling,sicilian-captures npm run benchmark:browser

Results (generated on 2026-02-24 UTC):

| Metric | chessiro-canvas | react-chessboard | Delta | | --- | ---: | ---: | ---: | | Mount wall time (mean) | 3.13 ms | 14.23 ms | 78.0% faster | | Update wall time (mean, 300 renders) | 277.42 ms | 733.11 ms | 62.2% faster | | Update wall per render (mean) | 0.92 ms | 2.44 ms | 62.2% faster | | React Profiler update duration (mean) | 0.22 ms | 1.33 ms | 83.4% faster | | Bundle ESM gzip | 31.41 KB | 37.38 KB | 16.0% smaller |

Notes:

  • Numbers will vary by machine, Node version, and benchmark config.
  • This benchmark is for relative comparison under the same harness, not an absolute browser FPS claim.

Development

npm install
npm run dev
npm run docs:dev
npm run build
npm run typecheck
npm run benchmark

License

MIT