Flutter Table Plus
A highly customizable, type-safe Flutter table widget with synchronized scrolling, sorting, selection, editing, and more.
β¨ Features
| Feature | Description |
|---|---|
| Type-Safe Generics | Works with any data model <T> β no more Map<String, dynamic> |
| Synchronized Scrolling | Header and body scroll together seamlessly |
| Sorting | Multi-state column sorting (ascending β descending β none) |
| Selection | Single or multiple row selection with checkboxes |
| Inline Editing | Click-to-edit cells with auto-save |
| Column Reordering | Drag-and-drop columns, drop on empty space to move to last |
| Column Resizing | Drag header edges to resize columns with min/max constraints and width persistence |
| Auto-Fit Columns | Double-tap resize handle to auto-fit, with custom override callback |
| Stretch Last Column | Last column fills remaining space when all columns are fixed |
| Drag Selection | Mouse drag to select row ranges with auto-scroll and a configurable rubber band rectangle (Finder/Explorer-style marquee) |
| Merged Rows | Group rows with custom merged content |
| Hover Buttons | Action buttons on row hover |
| Dynamic Row Heights | Support for variable height rows |
| Smart Tooltips | Text, widget, and whole-row tooltips, anchored beside the widget or beside the cursor |
| Dim Rows | Style inactive rows differently |
| Scale / Zoom | Ctrl/Cmd+wheel zoom with platform-aware modifier key, scroll-safe physics, automatic position correction, and optional blockModifierScroll control |
| Deep Theming | Nested theme classes down to individual borders, dividers and placeholder text |
| Minimal Dependencies | Only just_tooltip and flutter_checkbox |
π¦ Installation
dependencies:
flutter_table_plus: ^2.17.0
flutter pub get
π Quick Start
1. Define Your Data Model
class User {
final String id;
final String name;
final String email;
final int age;
const User({
required this.id,
required this.name,
required this.email,
required this.age,
});
}
2. Create the Table
import 'package:flutter_table_plus/flutter_table_plus.dart';
final columns = TableColumnsBuilder<User>()
..addColumn('name', TablePlusColumn(
key: 'name',
label: 'Name',
order: 1,
width: 150,
valueAccessor: (user) => user.name,
sortable: true,
))
..addColumn('email', TablePlusColumn(
key: 'email',
label: 'Email',
order: 2,
width: 200,
valueAccessor: (user) => user.email,
))
..addColumn('age', TablePlusColumn(
key: 'age',
label: 'Age',
order: 3,
width: 80,
valueAccessor: (user) => user.age,
sortable: true,
));
final users = [
User(id: '1', name: 'John Doe', email: 'john@example.com', age: 28),
User(id: '2', name: 'Jane Smith', email: 'jane@example.com', age: 34),
];
FlutterTablePlus<User>(
columns: columns.build(),
data: users,
rowId: (user) => user.id, // Unique identifier for each row
// Sorting
sortColumnKey: _sortColumn,
sortDirection: _sortDirection,
onSort: (columnKey, direction) {
setState(() {
_sortColumn = columnKey;
_sortDirection = direction;
// Sort your data here
});
},
// Selection
isSelectable: true,
selectionMode: SelectionMode.multiple,
selectedRows: _selectedRows,
onRowSelectionChanged: (rowId, isSelected) {
setState(() {
isSelected ? _selectedRows.add(rowId) : _selectedRows.remove(rowId);
});
},
)
3. Persist Column Widths
Save and restore user-resized column widths across sessions:
FlutterTablePlus<User>(
columns: columns.build(),
data: users,
rowId: (user) => user.id,
resizable: true,
// Restore saved widths (reacts to changes β safe with Riverpod watch)
initialResizedWidths: savedWidths, // e.g. {'name': 200, 'email': 150}
// Save widths when user resizes
onColumnResized: (columnKey, newWidth) {
savedWidths[columnKey] = newWidth;
// Persist to DB / SharedPreferences / etc.
},
)
π‘ Core Philosophy
Flutter Table Plus follows a UI-only, data-agnostic design:
- β You own your data β The package never stores or mutates your data
- β Callback-driven β All interactions flow through callbacks you control
- β
State management agnostic β Works with
setState, Provider, Riverpod, Bloc, etc. - β Maximum flexibility β No assumptions about your data structure or business logic
// You handle sorting
onSort: (columnKey, direction) {
// Sort your data however you want -- into a NEW list.
// `data` and `rowId` are read as one snapshot and invalidated on the
// list's identity, so an in-place `_myData.sort(...)` is not seen.
_myData = List.of(_myData)..sort(...);
}
// You handle selection
onRowSelectionChanged: (rowId, isSelected) {
// Update your selection state
_selectedIds.add(rowId);
}
// You handle editing
onCellChanged: (row, columnKey, rowIndex, oldValue, newValue) {
// Update your data model -- again into a NEW list. An in-place
// `_myData[rowIndex] = ...` renders the new value but leaves the cached
// row height, so a `calculateRowHeight` that measures the cell keeps the
// pre-edit height.
_myData = List.of(_myData)..[rowIndex] = row.copyWith(name: newValue);
}
π Documentation
| Guide | Description |
|---|---|
| Features Guide | Sorting, Selection, Editing, Merged Rows, Hover Buttons, and more |
| Theming Guide | Every theme class, what each field reaches, and what scaledBy scales |
| Migration Guide | Migrating from v1.x (Map) to v2.x (Generic<T>) |
π Links
π License
MIT License β see LICENSE for details.
Libraries
- flutter_table_plus
- A highly customizable and efficient table widget for Flutter.