super_table_field
A keyboard-first, generic Flutter data grid for ERP, accounting, inventory, and other data-heavy applications.
super_table_field provides a single SuperTable<R> widget for readable and editable workflows, backed by a SuperTableController<R>. It includes typed columns, validation, filtering, grouping, totals, pagination, change tracking, export, clipboard operations, undo/redo, expandable rows, runtime column configuration, and English/Arabic localization.
super_table_field exports only its own table APIs and localizations. It does not re-export super_core, super_auto_suggestion_box, or super_form_field. Import companion packages directly when application code references their APIs.
import 'package:super_table_field/super_table_field.dart';
Features
- Generic rows through
SuperRow<R>with support for map-backed and typed domain models. - Readable and editable modes that can be changed at runtime.
- Thirteen column types, including text, number, currency, enum, combo, date, checkbox, computed, and read-only columns.
- Inline editors based on
super_form_field; combo cells useSuperAutoSuggestionsBox. - Enumeration editors use the
super_form_field 1.12.0source-driven select API internally. - Single-cell, multi-cell, single-row, and multi-row selection modes.
- Search, per-column filters, and advanced cross-column filters.
- Multi-level grouping, group aggregates, group footers, and grand totals.
- Page, infinite-scroll, and load-more pagination modes.
- Column sorting, resizing, pinning, visibility, and runtime reordering.
- Table-wide validation, unique constraints, and jump-to-cell issue panels.
- Optional change tracking for added, modified, and deleted rows.
- CSV, TSV, JSON, clipboard, fill-down, fill-right, undo, and redo operations.
- Conditional row and cell styling.
- Optional
SuperTableStylepresets for calm ERP/accounting table styling. - Expandable detail rows.
- Interaction callbacks for cells, rows, selections, and sorting.
- English and Arabic localization with LTR and RTL support.
- Light and dark themes through
SuperMaterialThemeData.
Requirements
| Requirement | Minimum version |
|---|---|---
| Dart SDK | 3.8.0 |
| Flutter SDK | 3.32.0 |
| super_core | 3.6.0 |
| super_auto_suggestion_box | 1.3.2 |
| super_form_field | 1.12.0 |
Installation
Add the package to pubspec.yaml:
dependencies:
super_table_field: ^3.1.1
Then install the dependency:
flutter pub get
For local package development:
dependencies:
super_table_field:
path: ../super_table_field
Companion package imports
The table package keeps its public barrel focused on table-owned APIs. When your application directly uses a companion API, declare that package directly too. The package currently depends on:
dependencies:
super_core: ">=3.6.0 <4.0.0"
super_auto_suggestion_box: ">=1.3.2 <2.0.0"
super_form_field: ">=1.12.0 <2.0.0"
Typical cases are SuperTextTheme/SuperMaterialThemeData from super_core, SuperAutoSuggestionsItem or custom suggestion sources from super_auto_suggestion_box, and SuperSelectSource/SuperOption from super_form_field.
Application setup
Register the package localization delegates and use the super_core Material themes:
import 'package:flutter/material.dart';
import 'package:super_core/super_core.dart';
import 'package:super_table_field/super_table_field.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
final typography = SuperTextTheme();
return MaterialApp(
debugShowCheckedModeBanner: false,
localizationsDelegates:
SuperTableLocalization.localizationsDelegates,
supportedLocales: SuperTableLocalization.supportedLocales,
theme: SuperMaterialThemeData.light(
textTheme: typography,
primaryTextTheme: typography,
),
darkTheme: SuperMaterialThemeData.dark(
textTheme: typography,
primaryTextTheme: typography,
),
themeMode: ThemeMode.system,
home: const InventoryTablePage(),
);
}
}
super_core 3.6.0 requires explicit SuperTextTheme values for both
textTheme and primaryTextTheme. SuperThemeData no longer exposes
textTheme; read the branded typography through context.superTextTheme or
SuperMaterialThemeData.of(context).textTheme. The table follows the ambient
SuperTextTheme body/display/mono font families.
SuperTableLocalization.localizationsDelegates includes the package translation delegate together with Flutter's Material, Cupertino, and Widgets delegates. Registering it enables Arabic. Inside table widgets, use context.superTableLocalization; the extension returns the active package localization and falls back to built-in English strings when the delegate is not registered. The package currently supports:
const Locale('en');
const Locale('ar');
Quick start
Create the controller once, dispose it with the widget lifecycle, and place SuperTable inside a bounded layout such as Expanded, Flexible, SizedBox, or a widget with maxHeight.
import 'package:flutter/material.dart';
import 'package:super_table_field/super_table_field.dart';
class InventoryTablePage extends StatefulWidget {
const InventoryTablePage({super.key});
@override
State<InventoryTablePage> createState() => _InventoryTablePageState();
}
class _InventoryTablePageState extends State<InventoryTablePage> {
late final SuperTableController<Map<String, dynamic>> controller;
@override
void initState() {
super.initState();
controller = SuperTableController<Map<String, dynamic>>(
mode: SuperTableMode.editable,
selectionMode: SuperSelectionMode.multiCells,
addRowEnabled: true,
trackChanges: true,
emptyRowValue: () => <String, dynamic>{},
columns: [
SuperTextColumn(
key: 'sku',
label: 'SKU',
width: 140,
required: true,
unique: true,
mono: true,
),
SuperTextColumn(
key: 'name',
label: 'Product',
width: 220,
required: true,
),
SuperNumberColumn<int>(
key: 'quantity',
label: 'Quantity',
width: 110,
min: 0,
agg: SuperAgg.sum,
),
SuperComboColumn<String>(
key: 'unit',
label: 'Unit',
width: 120,
values: const ['Piece', 'Box', 'Carton'],
allowFreeText: false,
),
SuperCurrencyColumn(
key: 'price',
label: 'Unit price',
width: 130,
symbol: 'ر.ي',
code: 'YER',
min: 0,
agg: SuperAgg.sum,
),
SuperComputedColumn<num>(
key: 'total',
label: 'Total',
width: 140,
align: SuperAlign.end,
agg: SuperAgg.sum,
compute: (row) {
final quantity = row['quantity'] as num? ?? 0;
final price = row['price'] as num? ?? 0;
return quantity * price;
},
format: (value, row) =>
'${(value as num? ?? 0).toStringAsFixed(2)} YER',
),
SuperCheckboxColumn(
key: 'active',
label: 'Active',
width: 90,
),
],
rows: [
SuperRow.map({
'sku': 'PRD-001',
'name': 'Notebook',
'quantity': 12,
'unit': 'Piece',
'price': 850.0,
'active': true,
}),
SuperRow.map({
'sku': 'PRD-002',
'name': 'Printer paper',
'quantity': 4,
'unit': 'Box',
'price': 6200.0,
'active': true,
}),
],
onChange: (rows) {
// Persist or synchronize the changed rows in the host application.
},
onNotify: (kind, message) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
},
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Inventory'),
actions: [
IconButton(
tooltip: 'Toggle table mode',
onPressed: controller.toggleMode,
icon: const Icon(Icons.edit_outlined),
),
],
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Expanded(
child: SuperTable<Map<String, dynamic>>(
controller: controller,
columnFilters: true,
advancedFilter: true,
showTotals: true,
showFooter: true,
groupFooters: true,
),
),
],
),
),
);
}
}
showFooter controls pagination and load-more footer controls. It no longer adds
a persistent table status strip.
Core concepts
SuperTableController<R>
The controller owns the table state and data pipeline:
rows
→ search
→ column or advanced filters
→ sorting
→ grouping
→ pagination
→ rendered table
It also manages selection, editing, validation, row operations, clipboard operations, history, change tracking, column configuration, and load-more state.
Create the controller in initState, keep it stable across rebuilds, and call dispose() from the owning State object.
SuperRow<R>
Each row contains:
value: the host-owned backing model.cells: editable table values keyed by column key.id: stable row identity.fingerPrint: a rebuild token for per-row editor resources.
Use a map-backed row for simple data:
final row = SuperRow.map({
'code': '1001',
'name': 'Cash',
'balance': 25000,
});
final balance = row['balance'];
row['balance'] = 30000;
Use SuperRow.of for a typed domain model:
class Product {
Product({required this.id, required this.name, required this.quantity});
final int id;
String name;
int quantity;
}
final product = Product(id: 1, name: 'Notebook', quantity: 12);
final row = SuperRow<Product>.of(product, {
'name': product.name,
'quantity': product.quantity,
});
Use column read and write callbacks when values must be projected between cells and a typed backing model:
SuperTextColumn(
key: 'name',
label: 'Name',
read: (value) => (value as Product).name,
write: (value, next) => (value as Product).name = next,
);
SuperTable<R>
SuperTable is the view. It observes the controller and renders headers, filters, rows, editors, totals, pagination, overlays, and status information.
The table must receive bounded vertical space:
Expanded(
child: SuperTable<Map<String, dynamic>>(
controller: controller,
),
);
Or pass an explicit height constraint:
SuperTable<Map<String, dynamic>>(
controller: controller,
maxHeight: 520,
);
Column types
Use the typed column classes whenever possible. Instantiate SuperColumn<T> directly only for custom behavior that is not covered by the standard types.
| Column | Value type | Main use |
|---|---|---|---
| SuperTextColumn | String | General text and bilingual text through arKey |
| SuperNumberColumn<T> | int, double, or num | Quantities, rates, percentages, and numeric aggregates |
| SuperCurrencyColumn | num | Monetary values with symbol and optional code |
| SuperEnumerationColumn<T> | Any typed value | Strict pick-only selection |
| SuperComboColumn<T> | Any typed value | Suggestions with optional free text |
| SuperProgressColumn<T> | Numeric | Progress bar on a configurable range |
| SuperColorColumn<T> | Hex, integer, or Color | Color values and swatches |
| SuperDateColumn | String | YYYY-MM-DD date values |
| SuperTimeColumn | String | HH:mm time values |
| SuperLinkColumn | String | Clickable links |
| SuperCheckboxColumn | bool | Boolean values |
| SuperComputedColumn<T> | Derived | Read-only value computed from a row |
| SuperReadonlyColumn | String | Locked display value |
Shared column options
Common options include:
SuperTextColumn(
key: 'accountCode',
label: 'Account code',
width: 160,
align: SuperAlign.start,
pin: SuperPin.start,
editable: true,
sortable: true,
groupable: true,
filterable: true,
required: true,
unique: true,
hidden: false,
mono: true,
);
Important behaviors:
hidden: truekeeps the column available for filtering, grouping, and aggregation, but never renders or exports it.editable: nullinherits the table mode;trueorfalseoverrides it.unique: truevalidates non-empty values across all rows case-insensitively.formatterchanges displayed text only; sorting, filtering, grouping, and editing still use the raw value.readandwritemap table values to typed backing objects.
Enumeration columns with SuperSelectFormField
SuperEnumerationColumn<T> is strict pick-only editing backed by SuperSelectFormField<T>. The table still owns cell commit, validation, keyboard navigation, and row lifecycle; the select field owns option presentation and selection interaction.
For a local value set, no companion import is needed in application code:
SuperEnumerationColumn<String>(
key: 'status',
label: 'Status',
values: const ['Draft', 'Posted', 'Cancelled'],
searchable: true,
);
For source-driven values or custom option metadata, import super_form_field directly:
import 'package:super_form_field/super_form_field.dart';
import 'package:super_table_field/super_table_field.dart';
SuperEnumerationColumn<String>(
key: 'status',
label: 'Status',
sources: const [
SuperSelectListSource<String>(
items: ['Draft', 'Posted', 'Cancelled'],
),
],
searchable: true,
optionBuilder: (items, index, status) => SuperOption<String>(
value: status,
label: status,
description: 'Option ${index + 1} of ${items.length}',
),
);
When choices depend on other cells, use sourcesController. The table caches row-scoped select resources by row.fingerPrint; call row.randomFingerPrint() after changing the dependency:
SuperEnumerationColumn<String>(
key: 'bin',
label: 'Bin',
sourcesController: (context, controller, row, cell) {
final warehouse = row['warehouse'] as String?;
return [
SuperSelectListSource<String>(
items: binsByWarehouse[warehouse] ?? const [],
),
];
},
optionBuilder: (items, index, bin) => SuperOption(
value: bin,
label: bin,
),
);
Source resolution follows sourcesController → sources → values. Use cellController only when the host needs explicit row-scoped SuperSelectFieldController<T> control.
Editing and validation
Switch modes at runtime:
controller.setMode(SuperTableMode.editable);
controller.setMode(SuperTableMode.readable);
controller.toggleMode();
Column validation
Use validator for column-specific rules:
SuperNumberColumn<num>(
key: 'quantity',
label: 'Quantity',
min: 0,
validator: (context, controller, row, cell, value) {
if (value < 0) return 'Quantity cannot be negative';
return null;
},
);
Use onChange as a pre-commit hook. Return true to accept the new value and false to reject it:
SuperNumberColumn<num>(
key: 'debit',
label: 'Debit',
onChange: (context, controller, row, cell, previous, next) {
if (next > 0) row['credit'] = 0;
return next >= 0;
},
);
Table-wide validation
final issues = controller.validateAll();
if (issues.isEmpty) {
// Submit or post the rows.
}
Use the side-effect-free validity getter when only a boolean is needed:
final canSubmit = controller.isValid;
Show the built-in validation panel:
await showSuperValidationPanel(context, controller);
The panel lists every issue and allows the user to jump to the affected cell.
Per-cell edit locking
Use cellEditable to lock individual cells based on row state:
final controller = SuperTableController<Map<String, dynamic>>(
columns: columns,
rows: rows,
mode: SuperTableMode.editable,
cellEditable: (column, row) {
final posted = row['posted'] as bool? ?? false;
return !posted;
},
);
Change tracking
Enable change tracking when the host needs an add/modify/delete delta:
final controller = SuperTableController<Map<String, dynamic>>(
columns: columns,
rows: rows,
trackChanges: true,
);
Read the current delta:
final SuperChangeSet<Map<String, dynamic>> changes = controller.changes;
final addedRows = changes.added;
final modifiedRows = changes.modified;
final deletedRows = changes.deleted;
Manage the baseline:
controller.acceptChanges(); // The current rows become the new baseline.
controller.rejectChanges(); // Restore the captured baseline.
Revert a single value or row:
controller.revertCell(row, 'quantity');
controller.revertRow(row);
Filtering and search
Search
controller.setSearch('notebook');
Column filters
controller.setColumnFilter('active', true);
controller.setColumnFilter('status', 'Open');
controller.clearColumnFilters();
Advanced filter
controller.setAdvancedFilter([
const AdvancedFilterClause(
columnKey: 'quantity',
op: FilterOp.greaterOrEqual,
value: 10,
),
const AdvancedFilterClause(
columnKey: 'active',
op: FilterOp.equals,
value: true,
),
]);
Column filters and the advanced filter are mutually exclusive. Activating one deactivates the other.
Persist or restore filter state:
final json = controller.filterStateJson();
controller.applyFilterJson(json);
Filter option sources
Enum, currency, and color columns can receive static, asynchronous, or streaming filter options:
SuperEnumerationColumn<String>(
key: 'status',
label: 'Status',
values: const ['Draft', 'Posted', 'Cancelled'],
filterSource: FilterValueSource.async(() async {
return const [
FilterItem('Draft', 'Draft'),
FilterItem('Posted', 'Posted'),
FilterItem('Cancelled', 'Cancelled'),
];
}),
);
Grouping and aggregates
Declare an aggregate on a numeric column:
SuperNumberColumn<num>(
key: 'amount',
label: 'Amount',
agg: SuperAgg.sum,
);
Available reducers:
SuperAgg.none
SuperAgg.sum
SuperAgg.avg
SuperAgg.count
SuperAgg.min
SuperAgg.max
SuperAgg.custom
For a custom aggregate:
SuperNumberColumn<num>(
key: 'weightedRate',
label: 'Weighted rate',
agg: SuperAgg.custom,
aggregator: (rows) {
// Return the custom aggregate for the supplied rows.
return rows.length.toDouble();
},
);
Control grouping programmatically:
controller.setGroupKeys(['category', 'status']);
controller.toggleGroup('warehouse');
controller.clearGroups();
Read aggregate values:
final quantityTotal = controller.aggregateColumn('quantity');
final totals = controller.grandTotals();
final totalsByCategory = controller.aggregateBy(
'category',
'amount',
agg: SuperAgg.sum,
);
final groupTree = controller.groupAggregates();
Enable visual group subtotal rows:
SuperTable<Map<String, dynamic>>(
controller: controller,
groupFooters: true,
);
Pagination and loading
Select a pagination mode when creating the controller:
final controller = SuperTableController<Map<String, dynamic>>(
columns: columns,
rows: rows,
pagination: SuperPagination.pages,
pageSize: 25,
);
Available modes:
SuperPagination.none
SuperPagination.pages
SuperPagination.infinite
SuperPagination.loadMore
Change the mode or page programmatically:
controller.setPagination(SuperPagination.pages);
controller.setPage(2);
For remote loading, provide onLoadMore and update loading state after the request:
final controller = SuperTableController<Map<String, dynamic>>(
columns: columns,
rows: rows,
pagination: SuperPagination.loadMore,
hasMore: true,
onLoadMore: (filterState) async {
controller.setLoadMoreState(loadingMore: true);
try {
final nextRows = await repository.loadMore(filterState);
controller.appendRows(nextRows, hasMore: nextRows.isNotEmpty);
} finally {
controller.setLoadMoreState(loadingMore: false);
}
},
);
The active SuperFilterState is passed to onLoadMore, allowing the remote request to honor search and filter criteria.
Selection and interactions
Choose a selection mode:
controller.setSelectionMode(SuperSelectionMode.multiRows);
Available modes:
SuperSelectionMode.singleCell
SuperSelectionMode.multiCells
SuperSelectionMode.singleRow
SuperSelectionMode.multiRows
Programmatic selection:
controller.selectCellAt(0, 1);
controller.selectCells(const [CellPos(0, 0), CellPos(0, 1)]);
controller.selectRowAt(2);
controller.selectRowsAt([1, 2, 3]);
controller.selectAll();
controller.clearSelection();
Read spreadsheet-style statistics for numeric selected cells and render them where your application needs them:
final SuperSelectionStats? stats = controller.selectionStats;
SuperTable does not render a persistent row-count / shortcut / selection-statistics
status strip. selectionStats remains available as a controller API for custom UI.
Observe user and programmatic interactions:
SuperTable<Map<String, dynamic>>(
controller: controller,
interactions: SuperInteractions<Map<String, dynamic>>(
onCellTap: (details) {
debugPrint('Cell: ${details.column.key}');
},
onRowActivate: (details) {
openDetails(details.row.value);
},
onSelectionChanged: (selection) {
debugPrint('Selected cells: ${selection.cells.length}');
},
onSortChanged: (sort) {
debugPrint('Sort key: ${sort.columnKey}');
},
),
);
Interaction callbacks are observers. They do not replace the table's built-in selection, editing, sorting, or menu behavior.
Column management
The column manager is enabled by default in header menus:
SuperTable<Map<String, dynamic>>(
controller: controller,
columnManager: true,
);
Open it directly:
await showSuperColumnManager(context, controller);
Programmatic operations:
controller.setColumnPin('sku', SuperPin.start);
controller.cycleColumnPin('price');
controller.hideColumn('internalId');
controller.showColumn('internalId');
controller.toggleColumnVisible('status');
controller.moveColumn('price', 1);
controller.setManagedOrder(['sku', 'name', 'price', 'quantity']);
Saved views
A saved view can include column order, widths, visibility, runtime pins, sorting, grouping, collapsed groups, and optionally filters.
final Map<String, dynamic> json = controller.viewStateJson();
// Persist json per user and screen.
controller.applyViewJson(json);
Reset personalization:
controller.resetViewState();
Keep active filters while resetting columns and grouping:
controller.resetViewState(clearFilters: false);
Expandable rows
Add a detail panel below readable rows:
SuperTable<Map<String, dynamic>>(
controller: controller,
expansion: SuperRowExpansion<Map<String, dynamic>>(
mode: SuperRowExpansionMode.single,
defaultHeight: 140,
builder: (context, controller, row) {
return Padding(
padding: const EdgeInsets.all(16),
child: Text('Details for ${row['name']}'),
);
},
),
);
Use SuperRowExpansionMode.multi to allow multiple open rows. A heightBuilder can return a different expanded height per row.
Conditional styling
Cell styles
The first matching condition wins:
SuperNumberColumn<num>(
key: 'balance',
label: 'Balance',
colorSign: true,
styles: {
(context, controller, row, cell) => (cell.value as num? ?? 0) < 0:
const CellStyle(
foreground: Color(0xFFD32F2F),
fontWeight: FontWeight.w700,
),
},
);
Row styles
Pass row conditions to SuperTable:
SuperTable<Map<String, dynamic>>(
controller: controller,
styles: {
(context, controller, row) => row['cancelled'] == true:
const SuperRowStyle(
foreground: Color(0xFF8A8A8A),
),
},
);
Row styles take priority over cell styles.
Combo columns and suggestions
super_auto_suggestion_box is not re-exported. Import it directly when application code constructs suggestion sources, controllers, or SuperAutoSuggestionsItem metadata.
A static combo column:
SuperComboColumn<String>(
key: 'unit',
label: 'Unit',
values: const ['Piece', 'Box', 'Carton'],
allowFreeText: false,
clearButton: true,
);
SuperComboColumn follows super_auto_suggestion_box 1.3.2: suggestion data
and row-scoped sources use raw T values. Metadata is derived from optional
suggestionBuilder, then the column's display callback. Custom rows can
read the built SuperAutoSuggestionsItem<T> from itemBuilder.
The 1.3.2 suggestion box exposes selection through onSelectionChanged and
observes query text through SuperAutoSuggestionsController.text. The table
adapts these APIs internally while preserving SuperComboColumn's table-level
selection/free-text behavior. Sources stay bound to
SuperAutoSuggestionsBox, while controller instances own field state.
import 'package:super_auto_suggestion_box/super_auto_suggestion_box.dart';
import 'package:super_table_field/super_table_field.dart';
SuperComboColumn<String>(
key: 'account',
label: 'Account',
values: const ['1010 · Cash', '4000 · Revenue'],
suggestionBuilder: (items, index, account) => SuperAutoSuggestionsItem<String>(
value: account,
titleText: account.split(' · ').last,
descriptionText: account.split(' · ').first,
keywords: [account],
),
);
For suggestions that depend on the current row, provide sourceController or cellController. Update row.fingerPrint when dependent row data changes so the per-cell resources are rebuilt the next time the cell enters edit mode:
row.randomFingerPrint();
Rows and editing operations
controller.addRow();
controller.insertRowAfterFocus();
controller.insertRowBeforeFocus();
controller.duplicateRow();
controller.deleteRow();
controller.moveRowUp();
controller.moveRowDown();
controller.moveRow(0, 3);
Replace or append data:
controller.updateRows(nextRows);
controller.appendRows(nextRows);
controller.clearTable();
Replace the declared columns:
controller.updateColumns(nextColumns);
Clipboard, export, and history
Export the current filtered and sorted view:
final csv = controller.toCsv();
final tsv = controller.toTsv();
final jsonRows = controller.toJsonRows();
Copy data:
await controller.copyCsvToClipboard();
await controller.copyJson();
Spreadsheet operations:
controller.fillDown();
controller.fillRight();
controller.cutRange();
await controller.paste();
History operations:
if (controller.canUndo) controller.undo();
if (controller.canRedo) controller.redo();
Custom row menus
Extend the default row menu instead of replacing its standard actions:
SuperTable<Map<String, dynamic>>(
controller: controller,
rowMenuBuilder: (context, defaults) {
return [
...defaults,
SuperMenuEntry(
label: 'Open details',
separatorBefore: true,
icon: Icons.open_in_new,
onTap: () => openDetails(context.row.value),
),
];
},
);
Table appearance
Common view options:
SuperTable<Map<String, dynamic>>(
controller: controller,
density: SuperDensity.compact,
numbered: true,
showTotals: true,
showFooter: true,
formulaBar: true,
showCopyJsonButton: true,
showRedoUndoButtons: true,
columnFilters: true,
advancedFilter: true,
columnManager: true,
loading: false,
skeletonRows: 8,
);
Available density modes:
SuperDensity.comfortable
SuperDensity.compact
The visual system derives its colors, typography, spacing, and component behavior from the active SuperMaterialThemeData supplied by super_core.
Table styles
SuperTableStyle is optional. When style is null, no predefined table style is applied and the table keeps its existing/default appearance.
SuperTable<Map<String, dynamic>>(
controller: controller,
style: null,
);
Apply a predefined style explicitly:
SuperTable<Map<String, dynamic>>(
controller: controller,
style: SuperTableStyle.medium,
groupFooters: true,
showTotals: true,
);
Available presets:
SuperTableStyle.plainMinimal
SuperTableStyle.light
SuperTableStyle.medium
SuperTableStyle.dark
SuperTableStyle.accent
SuperTableStyle.bandedRows
SuperTableStyle.bandedColumns
SuperTableStyle.headerEmphasis
SuperTableStyle.gridBordered
SuperTableStyle.subtleBorders
Use SuperTableStyle.presets when building style pickers or comparison screens.
Style options mirror common office-table switches without changing table structure:
final style = SuperTableStyle.accent.copyWith(
options: const SuperTableStyleOptions(
showHeaderRow: true,
showFooterRow: true,
showTotalRow: true,
showGroupRows: true,
bandedRows: true,
bandedColumns: false,
emphasizeFirstColumn: true,
emphasizeLastColumn: true,
),
);
showFooterRow styles the existing group footer/subtotal rows rendered by SuperTable(groupFooters: true). showTotalRow styles the existing totals row rendered by showTotals: true. These options do not create rows, group data, or enable totals by themselves.
Custom styles can start from a preset and override selected areas:
final customLedgerStyle = SuperTableStyle.subtleBorders.copyWith(
name: 'Ledger review',
headerStyle: const SuperTableAreaStyle(
background: Color(0xFFEFF4F8),
foreground: Color(0xFF1F2937),
fontWeight: FontWeight.w800,
),
totalRowStyle: const SuperTableAreaStyle(
background: Color(0xFFE7EEF6),
fontWeight: FontWeight.w800,
),
borderStyle: const SuperTableBorderStyle(
dividerColor: Color(0xFFD7DEE8),
strongDividerColor: Color(0xFFB9C4D2),
),
);
Interaction styles for selected, hovered, focused, and disabled cells are resolved by the table renderer. Conditional SuperRowStyle and CellStyle overrides still take priority over preset body styling.
Localization and RTL
Set the application locale normally:
final typography = SuperTextTheme(isArabic: true);
MaterialApp(
locale: const Locale('ar'),
localizationsDelegates:
SuperTableLocalization.localizationsDelegates,
supportedLocales: SuperTableLocalization.supportedLocales,
theme: SuperMaterialThemeData.light(
textTheme: typography,
primaryTextTheme: typography,
),
darkTheme: SuperMaterialThemeData.dark(
textTheme: typography,
primaryTextTheme: typography,
),
home: const InventoryTablePage(),
);
Flutter automatically applies RTL layout for Arabic. Table menus, filters, validation messages, pagination labels, column management, shortcut help, and status text use the package localization delegate. Without that delegate, package-owned strings fall back to deterministic English.
Read package translations directly from a widget context when needed:
final translations = context.superTableLocalization;
Keyboard behavior
The table is designed for desktop and keyboard-heavy workflows. It supports cursor navigation, range selection, editing, clipboard operations, undo/redo, row operations, fill actions, and a shortcuts dialog.
Press F1 while the table is focused to open the built-in shortcut reference.
Use the controller's onKey callback to intercept application-specific shortcuts before the table applies its default behavior:
final controller = SuperTableController<Map<String, dynamic>>(
columns: columns,
rows: rows,
onKey: (context, controller, node, event) {
// Return true when the application handled the event.
return false;
},
);
Public API overview
Main types
| API | Purpose |
|---|---|---
| SuperTable<R> | Table view |
| SuperTableController<R> | State, data pipeline, editing, and commands |
| SuperRow<R> | Backing value and editable cells |
| SuperCell | Individual cell value and error state |
| SuperColumn<T> | Flexible base column |
| SuperInteractions<R> | Host interaction callbacks |
| SuperRowExpansion<R> | Expandable row configuration |
| SuperTableStyle | Optional table-wide style preset or custom style |
| SuperTableStyleOptions | Office-style switches for header, totals, banding, and column emphasis |
| SuperTableAreaStyle | Background, foreground, and weight for one table area |
| SuperTableBorderStyle | Outer border and divider styling |
| SuperFilterState | Search and filter state |
| SuperViewState | Persistable user view configuration |
| SuperChangeSet<R> | Added, modified, and deleted row delta |
| SuperValidationIssue<R> | Table-wide validation result |
| SuperSelectionStats | Numeric selection statistics |
| SuperGroupAggregate<R> | Programmatic grouping result |
Enums
| Enum | Values |
|---|---|---
| SuperTableMode | readable, editable |
| SuperSelectionMode | singleCell, multiCells, singleRow, multiRows |
| SuperPagination | none, pages, infinite, loadMore |
| SuperDensity | comfortable, compact |
| SuperAlign | start, center, end |
| SuperPin | none, left, right |
| SuperAgg | none, sum, avg, count, min, max, custom |
| FilterOp | Text, equality, comparison, range, and empty operators |
| SuperColorValue | hex, number, color |
| SuperRowExpansionMode | single, multi |
| SuperTableStylePreset | plainMinimal, light, medium, dark, accent, bandedRows, bandedColumns, headerEmphasis, gridBordered, subtleBorders |
Built-in overlays
| Function | Purpose |
|---|---|---
| showSuperMenu | Display a table-style menu |
| showSuperConfirm | Display a confirmation dialog |
| showSuperAdvancedFilter | Display the advanced filter editor |
| showSuperShortcuts | Display the keyboard shortcut reference |
| showSuperValidationPanel | Display validation issues with jump-to-cell |
| showSuperColumnManager | Reorder, show/hide, and pin columns |
Best practices
- Create and dispose
SuperTableControllerwith the widget lifecycle. - Keep the controller instance stable; do not recreate it from
build. - Give
SuperTablebounded height. - Use typed column classes instead of the generic base class when possible.
- Keep domain persistence and remote synchronization outside the widget; use controller callbacks and change sets to connect them.
- Use
SuperRow.ofwithreadandwritecallbacks for typed domain models. - Validate all rows before posting or saving.
- Persist
viewStateJson()per user and screen when column personalization is enabled. - Use
onLoadMorewith the suppliedSuperFilterStatefor remote pagination. - Dispose external streams, repositories, and suggestion sources in their owning application layer.
Additional documentation
License
This package is available under the MIT License.
Column width fitting (2.8.0)
Every column accepts widthFit:
SuperTextColumn(
key: 'sku',
label: 'SKU',
width: 130,
widthFit: SuperColumnWidthFit.none,
);
SuperTextColumn(
key: 'description',
label: 'Description',
widthFit: SuperColumnWidthFit.maxCell,
);
SuperNumberColumn<int>(
key: 'qty',
label: 'Qty',
widthFit: SuperColumnWidthFit.auto,
);
SuperTextColumn(
key: 'notes',
label: 'Notes',
width: 180,
widthFit: SuperColumnWidthFit.fit,
);
SuperColumnWidthFit.none keeps the declared (or typed default) width.
auto columns share the viewport width left after fixed/intrinsic/base widths
are reserved. maxCell measures the widest rendered row value, includes the
normal cell padding, and adds 30 px of measurement allowance to
avoid edge clipping/ellipsis. fit keeps its base width and shares any genuinely empty
viewport space with other fit columns.
A runtime controller.setWidth(key, px) override always wins and behaves as a
fixed width. Call controller.resetWidth(key) to restore the column's declared
widthFit behavior.
When auto and fit are mixed, auto resolves its equal viewport share first;
fit receives only surplus that is still empty afterward. If the resolved
columns are wider than the viewport, the existing horizontal scrolling behavior
is preserved.
Libraries
- localization/generated/l10n
- localization/generated/l10n_ar
- localization/generated/l10n_en
- localization/super_table_localizations
- super_table_field
- Super Table Field — a GeniusLink design-system Flutter package providing the
unified SuperTable data grid, wired to the SuperAutoSuggestionsBox typeahead
from the companion
super_auto_suggestion_boxpackage.