Examples
- Basic Usage
- With Sorting
- With Selection
- Row Reordering
- Border and Dividers


Simple data table with column headers.
Properties
Required
List<NeoTableColumn>
required
The column definitions that determine the table’s structure and headers.
List<NeoTableRow>
required
The data rows to display. Each row’s
cells map must include a widget for every visible column ID.Content
ValueChanged<String>
Called when a row is tapped. Receives the row ID.
ValueChanged<({String rowId, bool isHovered})>
Called when a row’s hover state changes. Receives the row ID and hover status.
String
The ID of the currently active row. The active row receives distinct highlight styling.
Widget
Widget to display when
rows is empty. Falls back to an empty table body if not provided.Layout
double
default:"48"
Fixed height for all rows in logical pixels.
double
default:"40"
Height of the sticky column-header row in logical pixels.
bool
default:"false"
Whether to show a border around the entire table.
bool
default:"false"
Whether to show horizontal divider lines between rows.
bool
default:"false"
Whether to show vertical divider lines between columns.
ScrollController
Scroll controller for the table body. Required when using
NeoTable.scrollToRow. An internal controller is used when not provided.Set of column IDs to hide. The columns remain in
columns so their cells are still defined in rows — they are simply not rendered.Sorting
NeoTableSortState
The current sort state (column ID + direction).
null means unsorted.NeoTableSortState
The sort state to fall back to when no explicit
sortState is set. The default-sort column cannot be fully unsorted — it cycles between ascending and descending.ValueChanged<NeoTableSortState?>
Called when the user clicks a sortable column header. Receives the new
NeoTableSortState, or null when returning to the unsorted state.Selection
bool
default:"false"
Whether rows can be selected with checkboxes. When
true, onSelectionChanged must be provided.Set<String>
The currently selected row IDs.
ValueChanged<Set<String>>
Called when the selection changes. Required when
isSelectable is true.Reordering
bool
default:"false"
Whether rows can be reordered by dragging. When
true, onRowReorder must be provided.Function(String rowId, int oldIndex, int newIndex)
Called when the user drops a row into a new position. Required when
isRowReorderable is true.bool
default:"false"
Whether columns can be reordered by dragging their headers. When
true, onColumnReorder must be provided.Function(String columnId, int oldIndex, int newIndex)
Called when the user drops a column header into a new position. Required when
isColumnReorderable is true.State
bool
default:"false"
Displays a loading indicator over the table body and disables interaction.
Static Helpers
NeoTable.scrollToRow()
Animates the table body scroll so the given row is aligned to the top of the viewport (below the fixed header).ScrollController
required
The same
ScrollController passed to the table’s scrollController prop.List<NeoTableRow>
required
The current row list — must match the table’s
rows prop.String
required
The ID of the row to scroll to.
double
required
Must match the table’s
fixedRowHeight prop.bool
default:"false"
Must match the table’s
showRowDividers prop so the scroll offset accounts for divider height.Column Properties
Required
String
required
A unique identifier for the column.
String
required
The display text for the column header.
Layout
int
default:"1"
Relative width of the column. Higher values take proportionally more space.
double
Minimum width in logical pixels. The column will not shrink below this value regardless of flex.
Sorting
bool
default:"false"
Whether clicking this column’s header triggers
NeoTable.onSortChanged.Reordering / Visibility
bool
default:"true"
Whether this column can be dragged when
NeoTable.isColumnReorderable is true.bool
default:"true"
Whether this column can be included in
NeoTable.hiddenColumnIds.Row Properties
Required
String
required
A unique identifier for the row.
Map<String, Widget>
required
A map from column ID to the cell widget. Every visible column must have a corresponding entry.
Enums
NeoTableSortDirection
Sort direction for a column. The absence of a sort state (i.e.null on the table) represents the unsorted state — there is no none value.
ascending: Sort from lowest to highest.descending: Sort from highest to lowest.
Best Practices
- Cell map keys: Use the exact column
idstrings ascellsmap keys. The table asserts in debug mode if any visible column’s ID is missing from a row’s cells. - Width constraints: Always provide a bounded width constraint (via
Expanded,SizedBox, etc.) so the table’s flex layout has a finite extent to divide. - Scroll to row: Pass a
scrollControllerwhenever you plan to callNeoTable.scrollToRow. - Reordering and sorting: Enabling both
isRowReorderableand a livesortStateat the same time can produce confusing UX — the reordered position conflicts with the sorted order. Consider disabling sorting while reordering is active.
Integration Notes
- The table validates that all row IDs are unique and that every visible column has a matching cell in each row, both via debug-mode assertions.
- Sort cycling for a sortable column: descending → ascending →
null(unsorted). When adefaultSortStateis set for the column, the cycle is descending → ascending only.









