A simple chess board widget, with several options.
Features

A simple chess board, where:
- you can configure the current position,
- you can configure each side type (e.g : we can drag pieces for white side, but block them for black side if we want to make an external engine move),
- you can show coordinates and player turn around the board,
- you define your own widget for processing with promotion piece selection,
- you can choose the orientation of the board (are Blacks at bottom ?),
- you can add arrows,
- you can choose colors,
- the common size will be the least of attributed width/height : if width > height => takes the allocated height, and the reverse if width < height (so for example, if you use it in a Row/Column, you can set the stretch alignment in the cross axis direction for a quite good layout/effect),
- you can highlights some square, from the color you want,
- you can enable interactive tap-to-move functionality with visual move indicators,
- you can customize move indicator widgets for both normal moves and capture moves,
- you can enable sound effects when moves are made (for both human and computer moves),
- you can track captured pieces for both players and get them as callbacks.
If you want to implement game logic, you can use the chess package.
Please, also notice, that when there is a pending promotion, you should prevent the user to reverse the board orientation. Otherwise the result can be quite ugly. This can easily be done by showing a modal over your interface when the user is invited to choose a promotion piece.
Last but not least : there's no implementation of cpu thinking, but you can grab a package for that. For example Stockfish Chess Engine.
Getting started
To use SimpleChessBoard widget, add simple_chess_board as a dependency in your pubspec.yaml .
Linux users
You will need alsa headers
For example on Ubuntu
sudo apt update
sudo apt install libasound2-dev
Usage
You can find a longer example in the example folder.
Simple example
SimpleChessBoard(
engineThinking: false,
fen: '8/8/8/4p1K1/2k1P3/8/8/8 b - - 0 1',
onMove: ({required ShortMove move}) {
debugPrint('${move.from}|${move.to}|${move.promotion}');
},
blackSideAtBottom: false,
whitePlayerType: PlayerType.human,
blackPlayerType: PlayerType.computer,
lastMoveToHighlight: BoardArrow(from: 'e2', to: 'e4'),
onPromote: () async => PieceType.queen,
onPromotionCommited: ({required moveDone, required pieceType}) => {},
onTap: ({required cellCoordinate}) {},
cellHighlights: <String, Color>{},
chessBoardColors: ChessBoardColors()
..lastMoveArrowColor = Colors.redAccent,
showPossibleMoves: false,
playSounds: true, // Enable sound effects for all moves
onCapturedPiecesChanged: ({
required whiteCapturedPieces,
required blackCapturedPieces,
}) {
// Track captured pieces - whiteCapturedPieces contains pieces captured by white
debugPrint('White captured: $whiteCapturedPieces');
debugPrint('Black captured: $blackCapturedPieces');
},
)
Customizing colors
SimpleChessBoard(
chessBoardColors: ChessBoardColors()
..lightSquaresColor = Colors.blue.shade200
..darkSquaresColor = Colors.blue.shade600
..coordinatesZoneColor = Colors.redAccent.shade200
..lastMoveArrowColor = Colors.cyan
..startSquareColor = Colors.orange
..endSquareColor = Colors.green
..circularProgressBarColor = Colors.red
..coordinatesColor = Colors.green,
engineThinking: false,
fen: _chess.fen,
onMove: tryMakingMove,
blackSideAtBottom: _blackAtBottom,
whitePlayerType: PlayerType.human,
blackPlayerType: PlayerType.human,
lastMoveToHighlight: _lastMoveArrowCoordinates,
cellHighlights: _highlightCells,
onPromote: () => handlePromotion(context),
onPromotionCommited: ({
required ShortMove moveDone,
required PieceType pieceType,
}) {
moveDone.promotion = pieceType;
tryMakingMove(move: moveDone);
},
onTap: ({required String cellCoordinate}) {
if (_highlightCells[cellCoordinate] == null) {
_highlightCells[cellCoordinate] = Colors.red.withAlpha(70);
setState(() {});
} else {
_highlightCells.remove(cellCoordinate);
setState(() {});
}
},
)
Customizing move indicators
SimpleChessBoard(
engineThinking: false,
fen: _chess.fen,
onMove: tryMakingMove,
blackSideAtBottom: _blackAtBottom,
whitePlayerType: PlayerType.human,
blackPlayerType: PlayerType.human,
showPossibleMoves: true,
// Custom widget for normal moves (empty squares)
normalMoveIndicatorBuilder: (cellSize) => SizedBox(
width: cellSize,
height: cellSize,
child: Center(
child: Container(
width: cellSize * 0.3,
height: cellSize * 0.3,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.blue.withValues(alpha: 0.7),
),
),
),
),
// Custom widget for capture moves (squares with opponent pieces)
captureMoveIndicatorBuilder: (cellSize) => Container(
width: cellSize,
height: cellSize,
decoration: BoxDecoration(
border: Border.all(
color: Colors.red,
width: cellSize * 0.05,
),
),
child: Icon(
Icons.close,
color: Colors.red,
size: cellSize * 0.5,
),
),
onPromote: () => handlePromotion(context),
// ... other required parameters
),
Move history and navigation
class ChessBoardWithHistory extends StatefulWidget {
@override
State<ChessBoardWithHistory> createState() => _ChessBoardWithHistoryState();
}
class _ChessBoardWithHistoryState extends State<ChessBoardWithHistory> {
late chess.Chess _chess;
late MoveHistory _moveHistory;
String? _customFen;
@override
void initState() {
super.initState();
_chess = chess.Chess();
_moveHistory = MoveHistory(); // Optional: MoveHistory(initialFen: customFen)
}
void _onMove(ShortMove move) {
// Make move and add to history
final success = _chess.move({'from': move.from, 'to': move.to});
if (success) {
_moveHistory.addMove(
move: '${move.from}${move.to}',
fen: _chess.fen,
);
setState(() => _customFen = null);
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
SimpleChessBoard(
fen: _customFen ?? _chess.fen,
onMove: ({required move}) => _onMove(move),
// Board is only interactive when at current position
isInteractive: _customFen == null,
// Customize non-interactive overlay
nonInteractiveText: 'ANALYZING POSITION',
nonInteractiveTextStyle: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 14,
),
nonInteractiveOverlayColor: Colors.deepPurple,
// ... other required parameters
),
// Navigation controls
MoveNavigationControls(
moveHistory: _moveHistory,
onGoBack: () => setState(() => _customFen = _moveHistory.goBack()),
onGoForward: () {
final fen = _moveHistory.goForward();
if (fen != null) setState(() => _customFen = fen);
},
onGoToStart: () => setState(() {
_customFen = _moveHistory.initialFen;
_moveHistory.goToIndex(-1);
}),
onGoToEnd: () => setState(() {
_customFen = null;
_moveHistory.goToIndex(_moveHistory.length - 1);
}),
),
],
);
}
}
Tracking captured pieces
You can track captured pieces by providing an onCapturedPiecesChanged callback. This callback is invoked whenever the board position changes, providing lists of captured pieces for both players:
class MyChessBoard extends StatefulWidget {
@override
State<MyChessBoard> createState() => _MyChessBoardState();
}
class _MyChessBoardState extends State<MyChessBoard> {
String fen = chess.Chess.DEFAULT_POSITION;
List<PieceType> whiteCapturedPieces = [];
List<PieceType> blackCapturedPieces = [];
@override
Widget build(BuildContext context) {
return Column(
children: [
// Display white's captured pieces (black pieces)
Text('White captured: ${whiteCapturedPieces.length} pieces'),
SimpleChessBoard(
fen: fen,
whitePlayerType: PlayerType.human,
blackPlayerType: PlayerType.human,
onMove: ({required move}) {
// Handle move
},
onCapturedPiecesChanged: ({
required whiteCapturedPieces,
required blackCapturedPieces,
}) {
setState(() {
this.whiteCapturedPieces = whiteCapturedPieces;
this.blackCapturedPieces = blackCapturedPieces;
});
},
// ... other required parameters
),
// Display black's captured pieces (white pieces)
Text('Black captured: ${blackCapturedPieces.length} pieces'),
],
);
}
}
The captured pieces are returned as lists of PieceType enum values, where:
whiteCapturedPieces: Black pieces that have been captured by whiteblackCapturedPieces: White pieces that have been captured by black
Each PieceType can be: pawn, knight, bishop, rook, queen, or king.
For a complete example with UI rendering of captured pieces, see example/lib/variants/captured_pieces_example.dart.
Handling promotion
You handle promotion in the function you give to the mandatory onPromote parameter. In this function you return the PieceType you want to use.
You can also be notified if the promotion has been set, by using onPromotionCommited function.
As an example:
SimpleChessBoard(
engineThinking: false,
fen: '1k6/p2KP3/1p6/8/4B3/8/8/8 w - - 0 1',
onMove: ({required ShortMove move}) {
debugPrint('${move.from}|${move.to}|${move.promotion}');
},
blackSideAtBottom: false,
whitePlayerType: PlayerType.human,
blackPlayerType: PlayerType.computer,
lastMoveToHighlight: null,
onPromote: () {
return showDialog<PieceType>(
context: context,
builder: (_) {
return AlertDialog(
title: Text('Promotion'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
title: Text("Queen"),
onTap: () =>
Navigator.of(context).pop(PieceType.queen),
),
ListTile(
title: Text("Rook"),
onTap: () =>
Navigator.of(context).pop(PieceType.rook),
),
ListTile(
title: Text("Bishop"),
onTap: () =>
Navigator.of(context).pop(PieceType.bishop),
),
ListTile(
title: Text("Knight"),
onTap: () =>
Navigator.of(context).pop(PieceType.knight),
),
],
),
);
},
);
},
onPromotionCommited: ({required moveDone, required pieceType}) {
// update the board logic with the given pieceType and moveData inside moveDone.
},
// Other parameters ...
)
Parameters
- fen : board position in Forsyth-Edwards Notation. Example :
rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1. - orientation: says if Black side is at bottom or not. Give
BoardColor.blackif Blacks must be at bottom of the board, orBoardColor.whiteotherwise. - whitePlayerType : if it is white turn and this is set to
PlayerType.human, then the user will be able to move pieces. Either with the click method, or with the drag and drop method. Otherwise, if set toPlayerType.computerand it is white turn, then the user won't be able to move pieces. - blackPlayerType : if it is black turn and this is set to
PlayerType.human, then the user will be able to move pieces. Either with the click method, or with the drag and drop method. Otherwise, if set toPlayerType.computerand it is black turn, then the user won't be able to move pieces. - onMove : the given function will be called whenever a move is done on board by the user (if he's allowed to move pieces). It has a single
requiredparameterShortMove movewhich carries data about from/to cells, as well as promotion type which is nullable. Notice that it's up to you to update the board or not based on the move you receive from this function. You can use the chess package to get the new position. - onPromote: the given function is called whenever a promotion move is done on board by the user (if he's allowed to move pieces). You must return a
Future<PieceType?>. TheFuturecan wrap anullvalue in order to cancel. Otherwise, wrap aPieceTypesuch asPieceType.queen. - onPromotionCommited : you should update the board logic here, as you get data about the move made and the selected promotion. The simplest way is to first change the promotion type of the given move data, and then try to update the board logic. (See example application code.)
- showCoordinatesZone (optionnal) : says if you want to show coordinates and player turn around the board. Give
truefor showing it, orfalsefor removing it. - lastMoveToHighlight (optionnal) : give data about the arrow to draw on the board, if any. You pass a
BoardArrowwith from/to cellsString(such asBoardArrow(from: 'e2', to: 'e4', color: Colors.Green)) if you want to draw an arrow, ornullif you don't want any arrow on the board. Given that colors can be customized in theChessBoardColorsinstance you give to the board. - highlightingArrows : gives the list of arrows to draw, excepting the last move arrow. You pass a list of
BoardArrow(see description of lastMoveToHighlight field). - showPossibleMoves (optional) : enable interactive tap-to-move functionality with visual move indicators. Set to
trueto show possible moves when a piece is selected. - normalMoveIndicatorBuilder (optional) : custom widget builder for normal move indicators (moves to empty squares). Receives the cell size as parameter and should return a widget.
- captureMoveIndicatorBuilder (optional) : custom widget builder for capture move indicators (moves to squares with opponent pieces). Receives the cell size as parameter and should return a widget.
- isInteractive (optional) : whether the board allows user interaction. When
false, displays a customizable overlay and prevents moves. Default:true. - nonInteractiveText (optional) : text to display when board is not interactive. Default:
'VIEWING HISTORY'. - nonInteractiveTextStyle (optional) : text style for the non-interactive overlay text.
- nonInteractiveOverlayColor (optional) : color for the non-interactive overlay border and background. Default:
Colors.orange. - engineThinking (optionnal) : says if you want to show a
CircularProgressBarin order to indicate that an engine is trying to compute next move for example. - playSounds (optional) : whether to play sound effects when moves are made. When
true, plays the castle.mp3 sound effect for each move (both human and computer moves). Default:false. - onCapturedPiecesChanged (optional) : callback function that is triggered whenever the board position changes. Receives two parameters:
whiteCapturedPieces(list ofPieceTyperepresenting black pieces captured by white) andblackCapturedPieces(list ofPieceTyperepresenting white pieces captured by black). Use this to track and display captured pieces in your UI. - onBoardResized (optionnal) : callback function that is triggered whenever the board is resized. Receives the double value
cellSizeas the parameter : the common side size of a single cell. - boardColors : you pass a
ChessBoardColorsin which colors can be customized. See example project code.
Project's repository
You can find the repository on Github.
Credits
- Using code from Flutter Chess board.
- Using code from Flutter Stateless Chess board.
- Using chess pieces definitions from Wikimedia Commons.