Worksheet Widget
A high-performance Flutter widget that brings Excel-like spreadsheet functionality to your app. Supporting 10%-400% zoom with GPU-optimized tile-based rendering.

Display and edit tabular data with smooth scrolling, pinch-to-zoom, and cell selection - all running at 60fps even with hundreds of thousands of rows.
๐ For Developers
- Developer Guide โ Prerequisites, project structure, and TDD workflow
- Architecture Overview โ Deep dive into the rendering pipeline and coordinate systems
- Performance Guide โ Tile cache tuning and large dataset strategies
- Full API Reference โ Quick reference for all classes and methods
Quick Start for Contributors
git clone https://github.com/sjhorn/worksheet.git
cd worksheet
flutter pub get
flutter run -t example/main.dart # Run the full demo
flutter test # Verify with all tests
๐ Performance at a Glance
The worksheet is built on three foundational technologies to achieve 60fps scrolling:
TwoDimensionalScrollable: Built-in Flutter 2D scroll managementLeafRenderObjectWidget: Direct render object control for custom high-speed paintingui.Picture/PictureRecorder: GPU-backed tile caching for efficient rendering
Benchmark SLAs (Automated in CI)
We maintain strict performance targets verified on every commit:
| Operation | Target | Description |
|---|---|---|
| Tile render | < 8ms | Max time to draw a 256px visible tile |
| Hit test | < 100ยตs | Latency to resolve screen tap to cell |
| Visible range | < 2ms | Viewport calculation at any scroll/zoom |
| Selection | < 1ms | Range selection even at Excel-scale |
| Resize | < 0.1ms | O(log n) row/column updates via BIT |
Try It In 30 Seconds
import 'package:flutter/material.dart';
import 'package:worksheet/worksheet.dart';
void main() => runApp(MaterialApp(home: MySpreadsheet()));
class MySpreadsheet extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Populate data efficiently with SparseWorksheetData
final data = SparseWorksheetData(rowCount: 100, columnCount: 10, cells: {
(0, 0): 'Name'.cell,
(0, 1): 'Amount'.cell,
(1, 0): 'Apples'.cell,
(1, 1): 42.cell,
(2, 1): '=2+42'.formula,
});
return Scaffold(
body: WorksheetTheme(
data: const WorksheetThemeData(),
child: Worksheet(
data: data,
rowCount: 100,
columnCount: 10,
),
),
);
}
}
Selection, Editing, and More
Add Selection and Editing
Want users to select and edit cells? Add a controller and callbacks:
class EditableSpreadsheet extends StatefulWidget {
@override
State<EditableSpreadsheet> createState() => _EditableSpreadsheetState();
}
class _EditableSpreadsheetState extends State<EditableSpreadsheet> {
final _data = SparseWorksheetData(rowCount: 1000, columnCount: 26);
final _controller = WorksheetController();
@override
Widget build(BuildContext context) {
return Scaffold(
body: WorksheetTheme(
data: const WorksheetThemeData(),
child: Worksheet(
data: _data,
controller: _controller,
rowCount: 1000,
columnCount: 26,
onCellTap: (cell) {
print('Tapped ${cell.toNotation()}'); // "A1", "B5", etc.
},
onEditCell: (cell) {
// Double-tap triggers edit - implement your editor UI
print('Edit ${cell.toNotation()}');
},
),
),
);
}
@override
void dispose() {
_controller.dispose();
_data.dispose();
super.dispose();
}
}
Format Your Numbers
Display values as currency, percentages, dates, and more using Excel-style format codes:
final data = SparseWorksheetData(rowCount: 100, columnCount: 10, cells: {
(0, 0): 'Revenue'.cell,
(0, 1): Cell.number(1234.56, format: CellFormat.currency), // "$1,234.56"
(1, 0): 'Growth'.cell,
(1, 1): Cell.number(0.085, format: CellFormat.percentage), // "9%"
(2, 0): 'Date'.cell,
(2, 1): Cell.date(DateTime(2024, 1, 15), format: CellFormat.dateIso), // "2024-01-15"
});
Automatic Type & Format Detection
Type values into cells and they're stored as the right type automatically:
// Detected automatically during editing and paste:
// "$1,234.56" โ CellValue.number(1234.56) format: currency
// "2025-01-15" โ CellValue.date(DateTime(2025, 1, 15)) format: dateIso
// "42%" โ CellValue.number(0.42) format: percentage
// "=SUM(A1:A5)" โ CellValue.formula('=SUM(A1:A5)')
Rich Text and Cell Merging
Style individual words within a cell and merge cells into regions:
final data = SparseWorksheetData(rowCount: 100, columnCount: 10, cells: {
// Rich text: inline bold + colored text in one cell
(0, 0): Cell.text('Total Revenue', richText: const [
TextSpan(text: 'Total ', style: TextStyle(fontWeight: FontWeight.bold)),
TextSpan(text: 'Revenue', style: TextStyle(color: Color(0xFF4472C4))),
]),
});
// Merge cells A1:D1 into a single wide cell
data.mergeCells(CellRange(0, 0, 0, 3));
Why This Widget?
- Sparse storage: Memory scales with data, not grid size (100K cells = ~20MB)
- 10%-400% zoom: Smooth pinch-to-zoom with automatic level-of-detail
- Full selection: Single cell, ranges, entire rows/columns, and multi-select
- Keyboard navigation: ~44 default bindings (Arrows, Tab, Enter, Ctrl+C/V, Ctrl+Z/Y, etc.)
- Formula features: Click cells to insert references, autocomplete function names
- Mobile support: Touch gestures, selection handles, pinch-to-zoom
- Theming: Full control over colors, fonts, headers โ built-in light/dark presets
Documentation Index
๐ User Guides
- Getting Started โ Installation, basic setup, enabling editing
- Cookbook โ Practical recipes for common tasks
- Theming Guide โ Colors, fonts, headers, and selection styles
- Mobile Interaction โ Touch gestures and handles
- Mouse Cursors โ Desktop cursor behavior and hit zones
๐งฉ Feature References
- Cell Merging โ Merge types, data rules, and rendering
- Cell Spillover โ Text overflow into adjacent empty cells
- Cell Referencing โ Formula editing and A1 notation
- Formula Autocomplete โ Autocomplete dropdown specification
๐ Internal Architecture & Performance
- Architecture โ Deep dive into the rendering pipeline
- Performance โ Tile cache tuning and benchmarks
- Testing Guide โ Unit tests, widget tests, and benchmarks
- Development Guide โ Contribution workflow and project structure
- Contributing โ How to propose changes and run checks
- Code of Conduct โ Community expectations
- Security โ Responsible disclosure
- Support โ Where to ask questions
Installation
Add to your pubspec.yaml:
dependencies:
worksheet: ^3.6.0
Keyboard Shortcuts
All shortcuts work out of the box. You can override or extend them via the shortcuts and actions parameters.
| Key | Action |
|---|---|
| Arrow keys | Move selection |
| Shift + Arrow | Extend selection |
| Tab / Shift+Tab | Move right/left |
| Enter / Shift+Enter | Move down/up |
| Home / End | Start/end of row |
| Ctrl+Home / Ctrl+End | Go to A1 / last cell |
| F2 | Edit current cell |
| Escape | Cancel active drag; or collapse range to single cell |
| Ctrl+C / Ctrl+X / Ctrl+V | Copy / Cut / Paste |
| Ctrl+Z / Ctrl+Y | Undo / Redo |
| Alt+Enter | Insert newline (when cell has wrapText) |
| Ctrl+B / Ctrl+I / Ctrl+U | Toggle bold / italic / underline (editing) |
| F4 | Cycle absolute/relative reference (formula editing) |
Examples & Demos
Run these from the example/ directory:
| File | Feature Demonstrated |
|---|---|
main.dart |
Full Demo: 50K rows, editing, resizing, zoom |
simple.dart |
Minimal smallest working worksheet |
merge.dart |
Cell merging with toolbar controls |
border.dart |
Complex border styles and junctions |
rich_text/ |
Inline styling with Google Fonts |
undo_redo.dart |
Full undo/redo history tracking |
autocomplete.dart |
Formula function autocomplete |
cd example
flutter run -t merge.dart
License
MIT License - see LICENSE for details.
Libraries
- worksheet
- High-performance Flutter worksheet widget with Excel-like functionality.