Skip to content

DataTable

A virtualized table for large collections of text rows.

See also: Table for small grids of widget cells · TreeTable for hierarchies.

Learn more: Lists & scrolling

Import: package:fleury/fleury.dart, or package:fleury/fleury_core.dart in browser code.

Choose a row

Use ↑ / ↓ to browse, then Enter to choose. A click chooses the row too. Watch Browsing and Chosen change separately.

datatable_rows.dart live
You write
import 'package:fleury/fleury_core.dart';
class TableRows extends StatefulWidget {
const TableRows({super.key});
@override
State<TableRows> createState() => _TableRowsState();
}
class _TableRowsState extends State<TableRows> {
final table = DataTableController();
String browsing = 'Row 1';
String chosen = 'None';
@override
void dispose() {
table.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 7,
child: DataTable(
controller: table,
rowCount: 100,
autofocus: true,
onFocusedItemChanged: (row) =>
setState(() => browsing = 'Row ${row + 1}'),
onSelect: (row) => setState(() => chosen = 'Row ${row + 1}'),
columns: const [
DataTableColumn(
id: 'name',
title: 'Name',
width: FixedColumnWidth(12),
),
DataTableColumn(id: 'status', title: 'Status'),
],
cellBuilder: (row, column) =>
column == 'name' ? 'Row ${row + 1}' : 'Ready',
),
),
Text('Browsing: $browsing'),
Text('Chosen: $chosen'),
],
);
}
Try it
Live · interactive
click & type to interact ⓘ how this demo runs

Select a cell range

Click a cell, then Shift-click another to select a range. Arrow keys move the underlined cursor; Space selects that cell. Shift-arrow extends a range from the cursor. Ctrl+C copies the range. Enter or a double-click opens a row.

datatable_cells.dart live
You write
import 'package:fleury/fleury_core.dart';
class TableCells extends StatefulWidget {
const TableCells({super.key});
@override
State<TableCells> createState() => _TableCellsState();
}
class _TableCellsState extends State<TableCells> {
String range = '1 × 1';
String status = 'No row opened';
@override
Widget build(BuildContext context) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 7,
child: DataTable(
rowCount: 100,
autofocus: true,
selectionMode: DataTableSelectionMode.cell,
onRangeChanged: (selection) => setState(() {
range = '${selection.rowCount} × ${selection.columnCount}';
}),
onAction: (row) => setState(() => status = 'Opened row ${row + 1}'),
onCopy: (result) => setState(
() => status =
'Copied ${result.export.rowCount} × ${result.export.columnCount}',
),
columns: const [
DataTableColumn(
id: 'name',
title: 'Name',
width: FixedColumnWidth(12),
),
DataTableColumn(id: 'status', title: 'Status'),
],
cellBuilder: (row, column) =>
column == 'name' ? 'Row ${row + 1}' : 'Ready',
),
),
Text('Range: $range'),
Text(status),
],
);
}
Try it
Live · interactive
click & type to interact ⓘ how this demo runs

onSelect confirms a row choice. In cell mode, onRangeChanged reports the selected cells and onAction runs the row command. Scrolling leaves the cursor and range unchanged.

For programmatic navigation, keep a DataTableController in state and set currentRowIndex or currentColumnIndex. Use selectCell to change the range. Controller writes do not call the interaction callbacks.

Row mode uses arrows to browse and click or Enter to choose. Cell mode keeps the navigation cursor separate from a range selected for copying; Enter or double-click runs a row command. The wheel only scrolls. Ctrl+C copies the current row or selected cell range as TSV or CSV. Clicking the header of a sortable column calls onSort; the header then shows the sort you pass back as sortColumnId and sortDirection.

Unlike Table, this widget does not mount every cell as a widget. It asks cellBuilder only for the visible body rows, paints those directly into the cell buffer, and contributes visible-row semantics from the render object.

ParameterTypeDefaultDescription
rowCount:intrequiredNumber of source rows available to the table.
shrinkWrap:boolfalseWhether an unbounded parent may size the table to all source rows. Keep false for virtualization; use Expanded in a Column or a SizedBox with height. Enable only for deliberately content-sized small tables.
columns:List<DataTableColumn>requiredColumn definitions, in display order.
cellBuilder:DataTableCellBuilderrequiredReturns the display text for a visible cell.
rowKeyBuilder:DataTableRowKeyBuilder?—Optional stable row identity used by semantics and copy callbacks.
controller:DataTableController?—External navigation and range controller. If omitted, the table owns one.
currentRowIndex:int?—Parent-owned browsing row. Supply onFocusedItemChanged to accept input requests by rebuilding with the requested row. Ignored requests leave the cursor at this value. Out-of-range values are clamped to the available rows. More
focusNode:FocusNode?—Focus node used for keyboard navigation.
autofocus:boolfalseWhether the table should request focus when mounted.
onSelect:void Function(int rowIndex)?—Confirms a row on a completed click, Enter, or semantic select/press. Available in row mode. Reconfirming the same row calls this again.
onAction:void Function(int rowIndex)?—Runs a row command on Enter, a completed double-click, or semantic press. Available in cell mode; selecting a cell range does not invoke it.
onFocusedItemChanged:void Function(int rowIndex)?—Reports user navigation to a different row, including pointer and semantic input. Column-only movement, scrolling, and controller writes do not fire it.
onRangeChanged:void Function(DataTableSelectionRange range)?—Reports a changed cell range after user selection, including semantic selection. Navigation alone and programmatic controller writes do not fire it. Available in cell mode.
typeahead:booltrueWhether typing a printable character moves the cursor to the next row whose first-column cell starts with it (grid type-ahead). On by default. Turn it off when the surrounding app binds bare printables (a vim-style command key, a q quit): a focused table with type-ahead on consumes every printable before those bindings see it.
selectionMode:DataTableSelectionModeDataTableSelectionMode.rowChoose rows with click/Enter, or select cell ranges and invoke a separate row command with double-click/Enter.
copySelectedRow:booltrueWhether Ctrl+C and semantic copy export the current selection.
copyOptions:DataTableCopyOptionsconst DataTableCopyOptions()Export and clipboard options used when copying table data.
onCopy:void Function(DataTableCopyResult result)?—Called after a copy attempt completes.
columnSpacing:int1Empty cells inserted between adjacent columns.
headerSeparator:booltrueWhether to draw a separator below the header row.
separatorStyle:CellStyle?—Style used for header and row separators.
selectedStyle:CellStyle?—Style merged onto the current row in row mode or selected cells in cell mode.
sortColumnId:String?—Id of the column your data is sorted by. If that column is sortable (DataTableColumn.sortable), its header shows ▲ or ▼ for sortDirection. The sort is also exposed through semantics; the table never reorders rows itself.
sortDirection:DataTableSortDirection?—Direction of the current sort: the sortColumnId header shows ▲ for ascending and ▼ for descending (^ and v where the terminal draws those symbols two cells wide). Also exposed through semantics.
onSort:void Function(String columnId)?—Called with a column’s id when the user clicks its header or activates it through semantics, only for columns whose DataTableColumn.sortable is true. The app sorts its data and chooses the direction, then rebuilds with sortColumnId and sortDirection to show the new sort.
filterText:String?—App-owned filter text exposed through semantics.
semanticLabel:String?'Data table'Semantic label for the table.
  • currentRowIndex: Update this and rowCount together when filtering or replacing data. This live value cannot be combined with controller. If omitted, the supplied controller or an internal controller owns navigation. It does not invoke onSelect or change the independently selected cell range.

DataTable is defined in packages/fleury/lib/src/catalog/data_table.dart.

Category: Lists & data · All widgets