Ultimate Grid

pub version pub points pub likes publisher CI GitHub stars license: MIT

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).

β–Ά 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 RenderObject paints only the visible cell window via a cached TextPainter LRU β€” 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 GridTheme floor with per-column / per-row / per-cell overrides flowing through one InteractionPolicy shape.

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, so Uint64List isn'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.dart import.