chessboard-react
v0.9.0
Published
Échiquier React contrôlé : coups au pointeur, flèches et cercles d'annotation, promotion. Sans état interne, sans design system imposé.
Maintainers
Readme
chessboard-react
Échiquier React contrôlé : il affiche une position, signale les coups et les flèches, et ne décide de rien. La partie, la navigation dans les coups et les boutons appartiennent à l'application.
pnpm add chessboard-react chess.jsreact et chess.js sont des dépendances peer : c'est votre version qui est
utilisée, jamais une seconde copie.
Utilisation
import { useState } from 'react'
import { Chess } from 'chess.js'
import { ChessBoard, cburnett } from 'chessboard-react'
import 'chessboard-react/styles.css'
export function Board() {
const [fen, setFen] = useState('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1')
return (
<ChessBoard
fen={fen}
pieces={cburnett}
onMove={move => {
const game = new Chess(fen)
game.move(move)
setFen(game.fen())
}}
/>
)
}onMove notifie, il ne joue pas. Le composant ne garde aucune copie de la
position : tant que vous ne repassez pas une nouvelle fen, rien ne bouge à
l'écran. Une seule source de vérité, la vôtre.
Les positions incomplètes des livres (sans roi, ou avec un seul) restent
jouables : l'échiquier charge chess.js avec skipValidation. Les règles
tiennent toujours — un roi en échec doit parer, et un camp sans roi n'est
jamais en échec ni mat. Pour appliquer ces coups, faites de même :
new Chess(fen, { skipValidation: true }). useChessGame le fait déjà.
Navigation dans une partie
L'échiquier ne sait afficher qu'une position. Pour parcourir les coups d'une
partie, le hook useChessGame s'en charge — séparément, parce que les
applications qui affichent une position sans historique (un diagramme, une
position issue d'une détection d'image) n'en ont pas besoin.
const game = useChessGame({ pgn })
<ChessBoard
fen={game.fen}
pieces={cburnett}
orientation={game.orientation}
lastMove={game.lastMove}
onMove={game.play}
/>
<button onClick={game.prev} disabled={!game.canPrev}>Précédent</button>
<button onClick={game.next} disabled={!game.canNext}>Suivant</button>
<button onClick={game.flip}>Retourner</button>Props
| Prop | Défaut | Rôle |
| --- | --- | --- |
| fen | — | Position affichée. Seule source de vérité. |
| pieces | — | Images des pièces. Obligatoire, voir plus bas. |
| orientation | 'white' | Camp affiché en bas. Prop pure : la changer retourne l'échiquier. |
| moves | 'play' | 'none' fige la position. Avec annotations="none", l'échiquier n'est plus qu'une image. |
| annotations | 'none' | 'draw' autorise le tracé de flèches et de cercles. |
| arrows | [] | Flèches et cercles affichés — toujours honorés, même avec annotations="none". |
| onArrowsChange | — | Le tracé remonte ici. Les flèches vous appartiennent. |
| dividers | [] | Traits qui partagent l'échiquier en plusieurs positions, voir plus bas. |
| onMove | — | Coup légal joué au pointeur. À vous de l'appliquer. |
| lastMove | — | { from, to } du dernier coup, surligné dans sa propre teinte. |
| clearArrowsOn | 'interaction' | 'move', 'interaction' ou 'never'. |
| showCoordinates | true | Lettres et chiffres dans les coins. |
| coordinateSize | 16 | Taille des coordonnées, en pourcentage du côté d'une case (8 à 40). |
| moveSound | 'wood' | 'wood', 'alabaster', une URL/data URI personnalisée, ou false. |
| labels | français | Tous les textes visibles et accessibles. |
Le survol et le curseur main ne sont posés que sur les cases qui répondent au
clic : les pièces au trait — y compris une pièce choisie, pour en changer — et
les destinations légales de la pièce choisie. Une case vide sans rapport ou une
pièce adverse ne s'allume jamais : l'échiquier ne promet rien qu'il ne tienne.
Au doigt, rien de tout ça n'apparaît (@media (hover: hover)).
Flèches et cercles
Un cercle est une flèche d'une case vers elle-même ({ from: 'e4', to: 'e4' }),
comme dans chessground : %cal et %csl du PGN tiennent dans le même tableau,
et tout ce qui transporte vos flèches transporte aussi les cercles.
Avec annotations="draw" :
| Geste | Souris | Doigt |
| --- | --- | --- |
| Symbole (cercle par défaut) | clic droit sur une case | double tap sur la case |
| Flèche | clic droit glissé | double tap, le doigt maintenu puis glissé |
| Couleur | touches tenues pendant le clic droit ; trois clics droits rapprochés : couleur suivante | triple tap : couleur suivante |
| Palette (couleurs et symboles) | clic droit maintenu une demi-seconde sur une case, ou trois clics droits dont le dernier maintenu : glisser jusqu'à l'option et relâcher, ou relâcher ailleurs puis cliquer | doigt maintenu une demi-seconde sans bouger (même sur une pièce), ou triple tap maintenu ; même principe |
| Tout effacer | clic gauche sur une case vide (clearArrowsOn), ou quatre clics droits rapprochés | quadruple tap, ou bouton de la palette |
Les couleurs à la souris reprennent les raccourcis de lichess : sans touche
la couleur choisie (green au départ), Shift (ou Ctrl) red, Alt (ou Meta,
ou AltGr) blue, les deux yellow. Retracer une annotation identique
l'efface ; sinon elle remplace celle de la case. La couleur et le symbole
choisis restent actifs jusqu'au prochain changement. Tout se fait d'une seule
main.
Une annotation sur une seule case accepte un shape, pour reproduire les
diagrammes des livres : circle (défaut), dot (rond), diamond (losange),
square (carré), frame (case encadrée), plus et minus.
<ChessBoard fen={fen} pieces={cburnett} arrows={[{ from: 'c6', to: 'c6', shape: 'plus' }, { from: 'e4', to: 'e4', shape: 'diamond', color: 'blue' }]} />Afficher et dessiner sont deux choses distinctes : un moteur peut suggérer des flèches sur un échiquier où l'utilisateur ne peut ni jouer ni annoter.
<ChessBoard fen={fen} pieces={cburnett} moves="none" annotations="none" arrows={engineLines} />Avec moves="none" annotations="none", aucun gestionnaire de pointeur n'est
posé et touch-action reste libre : la page défile normalement au doigt
au-dessus de l'échiquier, et le menu contextuel reste disponible.
Les pièces, et leur licence
pieces est obligatoire et sans valeur par défaut. C'est délibéré : un jeu
par défaut se retrouverait dans le bundle de tous les consommateurs, y compris
ceux qui fournissent le leur — avec les obligations de licence qui vont avec.
Le jeu fourni, cburnett, est celui de Wikipédia. Il est redistribué sous
licence BSD 3-clauses, qui impose de reproduire son avis de copyright dans
la documentation du produit qui le diffuse. Trois choses le rendent difficile à
perdre en route :
- Le module porte un commentaire
/*! … */, préservé par esbuild, terser et webpack jusque dans un bundle de production minifié. THIRD-PARTY-NOTICES.mdest publié avec le paquet.pieceCreditsexpose la mention pour l'afficher depuis votre interface :
import { pieceCredits } from 'chessboard-react'
<ul>
{pieceCredits.map(credit => (
<li key={credit.id}>
{credit.notice} <a href={credit.sourceUrl}>Source</a>
</li>
))}
</ul>Rendre ça sur une page de crédits ou dans vos mentions légales satisfait
l'obligation. Un consommateur qui fournit son propre jeu n'importe jamais
cburnett et n'est donc concerné par rien de tout ceci.
Son des déplacements
wood (« Bois feutré ») est joué par défaut chaque fois que la disposition
des pièces dans fen change. Le son confirme donc la position contrôlée : si
le parent refuse un coup signalé par onMove et conserve la même FEN, rien ne
retentit.
// Les deux sons CC0 inclus :
<ChessBoard fen={fen} pieces={cburnett} moveSound="wood" />
<ChessBoard fen={fen} pieces={cburnett} moveSound="alabaster" />
// Couper le son ou fournir le sien :
<ChessBoard fen={fen} pieces={cburnett} moveSound={false} />
<ChessBoard fen={fen} pieces={cburnett} moveSound="/sounds/move.mp3" />Les deux extraits sont embarqués en data URI : aucune ressource à copier dans
public/, aucune requête réseau. Ils sont publiés sous CC0 1.0, donc utilisables
et redistribuables commercialement sans attribution obligatoire. Le paquet
conserve tout de même leur provenance dans THIRD-PARTY-NOTICES.md et via
l'export soundCredits.
Traits de séparation
Les livres d'échecs montrent souvent deux positions sur un seul diagramme,
séparées par un trait : « à gauche, les Blancs jouent… ; à droite… ». Chaque
trait suit les bords des cases, d'un coin du quadrillage au suivant. Un coin
s'écrit [colonnes, rangées], compté de 0 à 8 depuis le coin inférieur gauche
de a1 :
// Entre les colonnes d et e, de haut en bas
<ChessBoard fen={fen} pieces={cburnett} dividers={[{ points: [[4, 0], [4, 8]] }]} />
// Entre les rangées 4 et 5
dividers={[{ points: [[0, 4], [8, 4]] }]}
// En escalier : entre d et e jusqu'à la 6e rangée, puis entre e et f
dividers={[{ points: [[4, 8], [4, 5], [5, 5], [5, 0]] }]}Les traits suivent l'orientation et sont tracés sous les pièces. Ils ne
changent rien au jeu : l'échiquier ne sait pas qu'il montre deux positions.
Leur couleur et leur épaisseur passent par --board-divider (la couleur du
cadre par défaut) et --board-divider-width (2px).
Thème
Toutes les couleurs passent par des variables CSS à valeur de repli — la feuille de style est correcte sans qu'aucune ne soit définie.
.mon-echiquier {
--board-light: #e7efe9;
--board-dark: #08643f;
--board-frame: #06301f;
--board-arrow-green: #d7a622;
--board-highlight: rgb(215 166 34 / 34%);
}Les autres : --board-frame-width, --board-shadow, --board-highlight-strong,
--board-legal-dot, --board-coordinate-light, --board-coordinate-dark,
--board-arrow-width,
--board-arrow-red, --board-arrow-blue, --board-arrow-yellow,
--board-circle-width, --board-backdrop,
--board-dialog-bg, --board-dialog-ink, --board-dialog-muted,
--board-radius, --board-radius-sm, --board-accent, --board-focus-ring,
--board-light-hover, --board-last-move, --board-hover-light,
--board-hover-dark.
La taille des coordonnées suit automatiquement celle de l'échiquier. Elle se
règle avec coordinateSize, en pourcentage du côté d'une case.
Rendu
L'échiquier est dessiné dans un seul SVG : cases, surbrillances, pièces, coordonnées et cadre partagent un même repère. Le navigateur cale chaque boîte CSS sur le pixel — à l'impression surtout, où le pixel CSS mesure 0,26 mm —, si bien qu'une grille de cases en HTML n'a pas toutes ses colonnes de la même largeur et que les pièces semblent glisser. Dans un SVG, rien n'est arrondi : un diagramme imprimé reste net et régulier.
Quand l'échiquier est interactif, une grille de boutons transparents est posée
par-dessus le dessin. Elle porte le focus au clavier, le nom des cases pour
les lecteurs d'écran (role="gridcell", « f4 Cavalier blanc ») et la cible des
gestes. Figé (moves="none" annotations="none"), l'échiquier n'a pas de
boutons : c'est une image (role="img") nommée par labels.board.
Pour styler le dessin, visez ses classes SVG : .board-square--light,
.board-square--dark, .board-highlight--selected, .board-highlight--last-move,
.board-pieces (dont l'ombre portée se retire avec filter: none, ce que la
feuille fait d'elle-même à l'impression), .rank-label, .file-label,
.legal-move-dot. Les boutons .square gardent les classes d'état
(square--selected, square--legal, square--playable…) mais ne dessinent
rien.
Avant la 0.5, les cases étaient des boutons qui portaient le dessin : les règles qui visaient
.square small,.square--lightou uneimg.piecene s'appliquent plus, et--board-coordinate-sizea disparu au profit decoordinateSize.
Les quatre emplacements de couleur s'appellent
green,red,blueetyellowparce que ce sont les codes couleur du PGN. Ce sont des noms d'emplacement, pas des couleurs : chacun se redéfinit par sa variable. Avant la 0.4,greenétait doré par défaut ; ce doré est désormais celui deyellow.
Développement
pnpm install
pnpm test # vitest + jsdom
pnpm run typecheck
pnpm run build # tsup → dist/Pour itérer depuis un projet consommateur sans republier :
pnpm add link:../chessboard-reactPublier
La publication est déclenchée par un tag :
pnpm version patch
git push --follow-tagsLe workflow .github/workflows/publish.yml rejoue types, tests et build avant
pnpm publish. Il attend le secret NPM_TOKEN (jeton « Automation » créé sur
npmjs.com).
Licence
MIT pour le code — voir LICENSE.
BSD 3-clauses pour les pièces Cburnett et CC0 1.0 pour les sons — voir
THIRD-PARTY-NOTICES.md.
