A lightweight, multi-framework data grid. One headless core, 10 UI packages across React, Angular, Vue, and vanilla JS.
Documentation · Getting Started · API Reference · Migrate from AG Grid · Discord Community
Pick the framework and UI library you already use and get sorting, filtering, pagination, cell editing, spreadsheet selection, and the shared core grid model out of the box.
| OGrid | AG Grid Community | AG Grid Enterprise | |
|---|---|---|---|
| Spreadsheet selection | Built-in | No | $999/dev/year |
| Clipboard (copy/paste) | Built-in | No | $999/dev/year |
| Fill handle (drag to fill) | Built-in | No | $999/dev/year |
| Undo / Redo | Built-in | No | $999/dev/year |
| Context menu | Built-in | No | $999/dev/year |
| Status bar | Built-in | No | $999/dev/year |
| Side bar | Built-in | No | $999/dev/year |
| Cell editing | Built-in | Built-in | Built-in |
| Sorting & filtering | Built-in | Built-in | Built-in |
| Bundle size (gzip) | 44-61 KB | ~339 KB | ~339 KB+ |
| License | MIT (free) | MIT | Commercial |
| Cost | $0 | $0 | $999/dev/year |
Bundle size is what you actually install (core + framework adapter + UI layer). See the architecture section for per-setup sizes.
Data
- Sorting: click headers to sort, multi-column sort, custom comparators
- Filtering: text search, multi-select checkboxes, date range, people picker (client or server-side)
- Pagination: configurable page sizes, client-side or server-side via
IDataSource - Virtual scrolling: row and column virtualization for large datasets
- Web worker sort/filter: offload to a background thread with
workerSort: true - Server-side data:
IDataSourcepattern for remote pagination, sorting, filtering - Column types: built-in
text,numeric,date,booleanwith auto-formatting and filters
Editing
- Cell editing: inline text, select, checkbox, rich select, and custom popup editors
- Clipboard: Ctrl+C / X / V with multi-cell copy/paste, respects
valueFormatter/valueParser - Fill handle: drag to fill cells (Excel-style)
- Undo / redo: full edit history with Ctrl+Z / Ctrl+Y, batch operation support
- Premium inputs: optional calendar date picker and more via
@alaarab/ogrid-{react,angular,vue,js}-inputs
Selection & Navigation
- Spreadsheet selection: click-and-drag range selection with active cell highlight
- Row selection: single or multiple with Shift+click range support
- Keyboard navigation: Arrow keys, Tab, Enter, F2, Home/End, Ctrl+Home/End, Ctrl+Arrow (Excel-style)
- Cell references: Excel-style column letters (A, B, C...), row numbers, name box showing active cell
Columns
- Column groups: multi-row grouped headers with arbitrary nesting
- Column pinning: sticky left/right columns
- Column resize: drag column borders to resize
- Column chooser: show/hide columns via toolbar dropdown or sidebar panel
- Column state persistence: save/restore visibility, sort, order, widths, filters
UI
- Toolbar & layout: unified bordered container with primary toolbar, secondary
toolbarBelowrow, and footer - Side bar: toggle-able panel with Columns and Filters panels
- Context menu: right-click with copy, paste, cut, export, undo/redo and keyboard shortcuts
- Status bar: row count, filtered count, selection aggregations (sum, avg, min, max)
- Empty state: custom message or render function
- CSV export: one-click export with formatted values
Advanced
- Grid API:
ref-based imperative API forsetRowData,getColumnState,selectAll, etc. - Formula engine: 159 built-in functions, Excel-like formula bar, cell reference highlighting, cross-cell recalculation
- Editor integration (MCP):
@alaarab/ogrid-mcpconnects your IDE to OGrid docs and lets it read and control a running grid - CSS containment: automatic
contain: contenton cells,content-visibility: autoon off-screen rows - TypeScript strict: fully generic
<T>with strict mode, zeroanyleaks
@alaarab/ogrid-core (pure TS, zero deps)
├── @alaarab/ogrid-react hooks + headless components
│ ├── ogrid-react-radix Radix UI views
│ ├── ogrid-react-fluent Fluent UI views
│ └── ogrid-react-material Material UI views
├── @alaarab/ogrid-angular signals + services
│ ├── ogrid-angular-material Angular Material views
│ ├── ogrid-angular-primeng PrimeNG views
│ └── ogrid-angular-radix Radix UI views
├── @alaarab/ogrid-vue composables
│ ├── ogrid-vue-vuetify Vuetify views
│ ├── ogrid-vue-primevue PrimeVue views
│ └── ogrid-vue-radix Radix UI views
└── @alaarab/ogrid-js vanilla JS (class-based)
Core owns types and pure TypeScript utilities with zero dependencies. Framework adapters (React hooks, Angular services, Vue composables) own state logic and headless components. UI packages are thin view layers, around 50 lines of framework-specific rendering per component. Shared test factories cover the common contract, while package-specific docs and E2E lanes track the remaining behavior gaps.
These are the actual sizes you ship. Each row is core + adapter + UI layer combined:
| Setup | Gzip |
|---|---|
| React + Radix | 54 KB |
| React + Fluent | 55 KB |
| React + Material | 57 KB |
| Angular + Material | 59 KB |
| Angular + PrimeNG | 61 KB |
| Angular + Radix | 59 KB |
| Vue + Vuetify | 47 KB |
| Vue + PrimeVue | 47 KB |
| Vue + Radix | 44 KB |
| Vanilla JS | 45 KB |
| AG Grid Community (comparison) | ~339 KB |
npm install @alaarab/ogrid-react-radiximport { OGrid, type IColumnDef } from '@alaarab/ogrid-react-radix';
const columns: IColumnDef<Employee>[] = [
{ columnId: 'name', name: 'Name', sortable: true, filterable: { type: 'text' } },
{ columnId: 'department', name: 'Department', filterable: { type: 'multiSelect' } },
{ columnId: 'salary', name: 'Salary', editable: true, type: 'numeric',
valueFormatter: (v) => `$${Number(v).toLocaleString()}` },
];
function App() {
return (
<OGrid
columns={columns}
data={employees}
getRowId={(e) => e.id}
editable
cellSelection
statusBar
/>
);
}Using Fluent UI? Change the import to
@alaarab/ogrid-react-fluent. Material UI?@alaarab/ogrid-react-material. Same API.
npm install @alaarab/ogrid-angular-material @angular/material @angular/cdkimport { Component } from '@angular/core';
import { OGridComponent, type IColumnDef } from '@alaarab/ogrid-angular-material';
@Component({
standalone: true,
imports: [OGridComponent],
template: `<ogrid [gridProps]="gridProps" />`
})
export class AppComponent {
gridProps = {
columns: [
{ columnId: 'name', name: 'Name', sortable: true, filterable: { type: 'text' } },
{ columnId: 'department', name: 'Department', filterable: { type: 'multiSelect' } },
{ columnId: 'salary', name: 'Salary', editable: true, type: 'numeric' },
] as IColumnDef[],
data: employees,
getRowId: (e: any) => e.id,
editable: true,
statusBar: true,
};
}Using PrimeNG? Change the import to
@alaarab/ogrid-angular-primeng. Radix UI?@alaarab/ogrid-angular-radix. Same API.
npm install @alaarab/ogrid-vue-vuetify vuetify<script setup lang="ts">
import { OGrid, type IColumnDef } from '@alaarab/ogrid-vue-vuetify';
const columns: IColumnDef[] = [
{ columnId: 'name', name: 'Name', sortable: true, filterable: { type: 'text' } },
{ columnId: 'department', name: 'Department', filterable: { type: 'multiSelect' } },
{ columnId: 'salary', name: 'Salary', editable: true, type: 'numeric' },
];
const gridProps = {
columns,
data: employees,
getRowId: (e: any) => e.id,
editable: true,
statusBar: true,
};
</script>
<template>
<OGrid :gridProps="gridProps" />
</template>Using PrimeVue? Change the import to
@alaarab/ogrid-vue-primevue. Radix UI?@alaarab/ogrid-vue-radix. Same API.
npm install @alaarab/ogrid-jsimport { OGrid } from '@alaarab/ogrid-js';
const grid = new OGrid(document.getElementById('grid')!, {
columns: [
{ columnId: 'name', name: 'Name', sortable: true, filterable: { type: 'text' } },
{ columnId: 'department', name: 'Department', filterable: { type: 'multiSelect' } },
{ columnId: 'salary', name: 'Salary', editable: true, type: 'numeric' },
],
data: employees,
getRowId: (e) => e.id,
editable: true,
cellSelection: true,
});
// Programmatic control
grid.getApi().setRowData(newData);
grid.destroy();OGrid supports multiple editor types out of the box:
<OGrid
columns={[
{ columnId: 'name', name: 'Name', editable: true },
{ columnId: 'status', name: 'Status', editable: true,
cellEditor: 'select', cellEditorParams: { values: ['Active', 'Inactive'] } },
{ columnId: 'verified', name: 'Verified', editable: true, cellEditor: 'checkbox' },
]}
data={data}
getRowId={(r) => r.id}
editable
onCellValueChanged={(e) => console.log(e.columnId, e.oldValue, '->', e.newValue)}
/>Built-in editors: text (default), select, checkbox, date, richSelect, and custom popup editors via cellEditor component.
Access the imperative API via a ref for programmatic control:
const gridRef = useRef<IOGridApi<Product>>(null);
<OGrid ref={gridRef} data={products} columns={columns} getRowId={(r) => r.id} />
// Programmatic control
gridRef.current?.setRowData(newData);
gridRef.current?.setFilterModel({ status: ['Active'] });
gridRef.current?.selectAll();
// Save/restore column state (localStorage, database, etc.)
const state = gridRef.current?.getColumnState();
gridRef.current?.applyColumnState(savedState);Use the IDataSource interface for remote pagination, sorting, and filtering:
import type { IDataSource } from '@alaarab/ogrid-core';
const dataSource: IDataSource<Product> = {
async fetchPage({ page, pageSize, sort, filters }) {
const res = await fetch(`/api/products?page=${page}&pageSize=${pageSize}`);
return res.json(); // { items: Product[], totalCount: number }
},
async fetchFilterOptions(field) {
const res = await fetch(`/api/products/distinct/${field}`);
return res.json(); // string[]
},
};
<OGrid dataSource={dataSource} columns={columns} getRowId={(r) => r.id} />Core features are shared across React, Angular, Vue, and vanilla JS. Main CI now stays fast with lint plus a browser smoke suite on every push, while the heavier verification workflows are run manually when you want a full release-grade pass.
| Package | npm | Peer Dependencies |
|---|---|---|
@alaarab/ogrid-core |
None | |
| React | ||
@alaarab/ogrid-react |
react, react-dom |
|
@alaarab/ogrid-react-radix |
react, react-dom |
|
@alaarab/ogrid-react-fluent |
+ @fluentui/react-components, @fluentui/react-icons |
|
@alaarab/ogrid-react-material |
+ @mui/material, @mui/icons-material, @emotion/* |
|
| Angular | ||
@alaarab/ogrid-angular |
@angular/core, @angular/common |
|
@alaarab/ogrid-angular-material |
+ @angular/material, @angular/cdk |
|
@alaarab/ogrid-angular-primeng |
+ primeng |
|
@alaarab/ogrid-angular-radix |
None extra | |
| Vue | ||
@alaarab/ogrid-vue |
vue |
|
@alaarab/ogrid-vue-vuetify |
+ vuetify |
|
@alaarab/ogrid-vue-primevue |
+ primevue |
|
@alaarab/ogrid-vue-radix |
None extra | |
| Vanilla JS | ||
@alaarab/ogrid-js |
None |
UI packages re-export everything from their adapter (which re-exports from core), so one import is all you need.
Optional premium inputs (calendar date picker, rating, color picker, slider, tags) are available as add-on packages: @alaarab/ogrid-react-inputs, @alaarab/ogrid-angular-inputs, @alaarab/ogrid-vue-inputs, @alaarab/ogrid-js-inputs.
@alaarab/ogrid-mcp is a standalone MCP server that gives your IDE full access to OGrid documentation and lets it read and control a running grid in real time.
# One-time setup (any MCP-compatible editor)
npx -y @alaarab/ogrid-mcp
# Or add to your editor's MCP config
{
"mcpServers": {
"ogrid": { "command": "npx", "args": ["-y", "@alaarab/ogrid-mcp"] }
}
}Once connected, your editor can search and read the full OGrid documentation:
> Which filtering modes does OGrid support?
> Show me a server-side data source example in Angular
> How do I pin columns in Vue?
Available tools: search_docs, list_docs, get_docs, get_code_example, detect_version
Available resources: ogrid://quick-reference, ogrid://docs/{path}, ogrid://migration-guide
Add --bridge to let your editor read and control a running OGrid instance:
npx @alaarab/ogrid-mcp --bridgeThen connect your dev app with one useEffect:
import { connectGridToBridge } from '@alaarab/ogrid-mcp/bridge-client';
useEffect(() => {
const bridge = connectGridToBridge({
gridId: 'my-grid',
getData: () => data,
getColumns: () => columns,
onCellUpdate: (rowIndex, columnId, value) =>
setData(prev => prev.map((row, i) => i === rowIndex ? { ...row, [columnId]: value } : row)),
});
return () => bridge.disconnect();
}, [data]);Now your editor can inspect what's actually rendering, update cells, apply filters, and navigate pages while you watch the grid update live.
Bridge tools: list_grids, get_grid_state, send_grid_command
Note: The bridge is dev-only and localhost-only. Never run
--bridgein production.
See the MCP guide and live testing bridge guide for full documentation.
4,999 tests across all packages. Each framework uses its native testing tools:
| Framework | Tool | Tests |
|---|---|---|
| Core | Jest + ts-jest | ~1,501 |
| React | React Testing Library | ~903 |
| Angular | Angular Testing utilities | ~706 |
| Vue | Vue Test Utils | ~768 |
| Vanilla JS | Native DOM + jsdom | ~394 |
Cross-package parity is driven by shared test factories: 8 factories per framework generate the common scenarios, and Playwright now covers a fast smoke gate on every push plus a manual full matrix across all 10 example apps.
git clone https://github.com/alaarab/ogrid.git
cd ogrid
npm install
npm run build # Build all packages (Turborepo)
npm run test:all # Run all tests
npm run lint # ESLint
npm run test:e2e:smoke # Browser merge gate (React Radix, Angular Material, Vue Vuetify, JS)
npm run test:e2e:docs # Built docs homepage verification
npm run test:e2e:matrix # Full browser matrix across all 10 example apps
# GitHub Actions
# CI -> fast push/PR checks (lint + browser smoke)
# Full Verification -> manual full build/test matrix before release or larger merges
# Playwright Matrix -> manual browser parity pass across all 10 example apps
# Storybook
npm run storybook:react-fluent # React Fluent UI (port 6006)
npm run storybook:react-material # React Material UI (port 6007)
npm run storybook:react-radix # React Radix UI (port 6008)
npm run storybook:vue-vuetify # Vue Vuetify (port 6011)
# Documentation
npm run docs:dev # Docusaurus dev server
npm run docs:build # Build docs site- Node.js >= 18 (developed with Node 22)
- npm workspaces + Turborepo for monorepo management
Contributions are welcome. To get started:
- Fork the repository and create a feature branch.
- Make your changes following the project conventions (TypeScript strict, ESM-first, headless architecture).
- If your change affects UI, update all UI packages within the relevant framework(s) to maintain parity.
- Add or extend tests. Use the shared test factories so all UI packages get coverage.
- Run the full verification suite before submitting:
npm run build && npm run test:all && npm run lint && npm run test:e2e:smoke- Open a pull request with a clear description of what changed and why.
See ARCHITECTURE.md for detailed architecture documentation and conventions.
Built by Ala Arab