Ultimate Grid
A scalable, themable 2D data-grid for Flutter β millions of cells without
jitter, a 9-region freeze layout, cell merges, async data, Excel-style
selection, sort / filter / search, and full theming. Free, open source, and
framework-agnostic (the library depends only on flutter/widgets.dart).
π Links
βΆ Live demo & examples Β· π Docs Β· π§© API reference Β· πΊοΈ Roadmap & limitations Β· pub.dev Β· GitHub
Live demo goes live on the first push to
main(GitHub Pages workflow).
Why Ultimate Grid
- Scales. A custom
RenderObjectpaints only the visible cell window via a cachedTextPainterLRU β no widget tree per cell. Smooth at millions of rows. - Freezes like a spreadsheet. 9 regions: left / right-frozen columns Γ top / bottom-frozen rows, including non-contiguous freezes by pin priority.
- Framework-agnostic. Core uses only
flutter/widgets.dartβ no Material or Cupertino lock-in. Plug your own popup/dialog UI (shadcn, Material, β¦) into the column menu and filter dialog via builder callbacks. - Themable to the cell. A
GridThemefloor with per-column / per-row / per-cell overrides flowing through oneInteractionPolicyshape.
Install
dependencies:
ultimate_grid: ^0.1.1
import 'package:ultimate_grid/ultimate_grid.dart';
Quick start
final schema = GridSchema(
columns: const [
ColumnSpec(id: 'sku', header: 'SKU', defaultWidth: 110,
defaultFrozen: FrozenSide.start),
ColumnSpec(id: 'name', header: 'Product', defaultWidth: 220),
ColumnSpec(id: 'price', header: 'Price', defaultWidth: 100,
kind: CellKind.number),
],
rows: [for (var i = 0; i < 100; i++) RowSpec(id: 'r$i')],
);
final source = MapGridDataSource(
rowIds: [for (final r in schema.rows) r.id],
colIds: [for (final c in schema.columns) c.id],
);
source.setValue('r0', 'sku', const TextCell('SKU-0001'));
source.setValue('r0', 'name', const TextCell('Widget A'));
source.setValue('r0', 'price', const NumberCell(19.99));
final controller = GridController(schema: schema, source: source);
// In your widget tree:
UltimateTable(controller: controller);
A fuller gallery β financial sheet with merges, 100k-row async paging, search &
filter UI, a theme switcher with three presets, office time log, budget tracker,
datagrid, spreadsheet, and a 5 M-row stress test β ships in example/
and on the live demo.
Documentation
Docs live in docs/ and are the single source of truth. Start at the
docs index, then read in order:
| Guide | What it covers |
|---|---|
| Getting started | Install, the minimal grid, running the example |
| Concepts | Schema vs. source vs. controller vs. view; the 9 regions |
| Columns | Width, freeze / pin, resize, reorder, hide, alignment, headers |
| Cells & rendering | CellValue kinds, default + custom renderers, widget cells |
| Data sources | MapGridDataSource, async paging, sparse data, merges |
| Interaction | Selection, clipboard TSV, in-cell editor, keyboard nav, policies |
| Sort / filter / search | Column menu, Filters.*, the view pipeline, search modes |
| Theming | GridTheme fields, presets, per-column / row / cell overrides |
| Performance | Canvas paint, paragraph cache, large-data and web notes |
| Recipes | Advanced: custom renderers, totals rows, custom menu UI |
API specifics (every public symbol) are on pub.dev/documentation.
Feature matrix
β shipped Β· π§ in progress Β· ποΈ planned β full detail in docs/STATUS.md.
| Capability | Status |
|---|---|
Headless model (GridSchema / GridDataSource / GridController) |
β |
| 9-region freeze (non-contiguous, pin priority) | β |
| Custom-paint body, no widget tree per cell (~5M rows) | β |
Cell merges (MergeRange) |
β |
| Excel-style selection + TSV clipboard copy | β |
| In-cell editor (double-tap, Enter / Esc) | β |
| Sort / filter / search (single-pass pipeline) | β |
| Column menu + type-aware filter dialog | β |
| Drag-resize + drag-reorder columns | β |
| Async / paged data source | β |
| Pluggable cell renderers + widget cells | β |
GridTheme + per-column / row / cell overrides |
β |
| Showcase site + tiered docs | π§ |
| CSV / Excel export | ποΈ |
| Pagination widget Β· row grouping Β· frozen totals | ποΈ |
| RTL Β· accessibility Β· column virtualization | ποΈ |
Constraints
- Flutter web: bitsets use
Uint32List(JavaScript has no native 64-bit ints, soUint64Listisn't supported on web). - No widget tree per cell in the body. Body cells are painted directly, so drag-to-highlight a substring inside one cell is intentionally unsupported β double-tap to open the in-cell editor for full text selection.
See docs/STATUS.md for the complete list of limitations and known issues.
Architecture map
src/
βββ model/ CellValue, CellAddress, ColumnSpec, RowSpec, GridSchema, FrozenSide
βββ source/ GridDataSource (abstract) + MapGridDataSource + AsyncGridDataSource
βββ interaction/ InteractionPolicy: MapPolicy, PredicatePolicy, composition
βββ controller/ Selection, ColumnLayout, RowLayout, GridController, clipboard
βββ filter_sort/ ViewPipeline (filter β sort β search; one pass) + Filters
βββ theme/ GridTheme, ColumnStyle, RowStyle, CellStyle
βββ cells/ CellRenderer + registry; default Number/Text/Bool/Date renderers
βββ view/ UltimateTable, header, render body, column menu, search field
Why this package exists (the origin story)
Around 2017β2020, I was a one-person stack working on a tablet app for a
construction company. The headline screen was a timesheet β crews on one axis,
cost codes on the other, hours in the middle, with phases and projects layered
behind them in a relational backend. It was the most complex piece of UI I had
built up to that point: cell mapping across schemas, multi-cell selection,
multi-value entry, a custom on-screen keyboard, and the kind of state churn
where every edit had to ripple into totals on the right and a
quantity-to-claim band on the top. On the web side the same dataset was shown
through a jQuery-driven bootstrap-table, with PHP, SQL, CSS, the Flutter app,
and the servers all in the same week's todo list.
I shipped it. It worked. It was also a stark reminder that Flutter, at the
time, had no real grid ecosystem. table_sticky_headers covered the basic
sticky-header case but wasn't flexible enough. When TwoDimensional arrived in
the SDK a couple of years later I was hopeful β but it landed as a low-level
building block, not a feature-grade grid, and the gap between "drawable
viewport" and "actual datagrid" stayed wide.
Between contract gigs I kept the unfinished grid in a side folder. It was too
unpolished to publish β always five missing pieces. With AI-assisted overhauls
over the last year I finally did the surgery it needed: replaced the external
table dependency with the canvas-paint body it has today, kept the original
mental model (rows and columns are both data, edges freeze, totals derive), and
produced something I could share. The same time-log shape that motivated this
package ships as the Office Time Log demo in example/ today, reframed for
IT projects on the new engine.
Five clients later, the same package now ships under multiple production apps. None of them are construction-shaped.
This is the artifact of that journey: the grid I wanted in 2018, written through 2026, for everyone who's hit the same wall.
Contributing
See CONTRIBUTING.md. The hard rules inside lib/: web-safe
bitsets only, no widget tree per cell on the paint path, doc comments on every
public symbol.
License
MIT β see LICENSE.
Libraries
- ultimate_grid
- Ultimate Table by CodeBigya β a scalable, themable 2D data-grid for Flutter.
- ultimate_grid_material
- Deprecated β all exports are now available from the core
package:ultimate_grid/ultimate_grid.dartimport.