opencorestack/opengridx

High-performance React data grid infrastructure. Unlock advanced Row Grouping, Excel Export, and Column Pinning without the usual "Pro" gatekeeping. Built for speed, scale, and complete architectural freedom. Fully open, virtualization-ready, and feature-complete.

TypeScript

2

27 commits

updated Oct 1, 2026

See the code

See what people are saying

SourceMessageScoreDate

Tired of "Enterprise" paywalls on React Data Grids, so I built OpenGridX (MIT) (r/reactjs)

​Hey everyone, ​Every React dev has been here: you pick a popular data grid library for an internal tool or client dashboard. Everything works great until a product manager asks for column pinning, row grouping, or exporting to Excel. ​Suddenly, your options are: ​Explain to management why they…

0

Oct 1, 2026

README

OpenGridX | The Free Enterprise React DataGrid

OpenGridX Hero Banner

OpenGridX is a premium, high-performance React DataGrid engine designed to break the "Pay-to-Play" model in the React ecosystem. It provides a pure-custom, zero-dependency alternative to enterprise grids like MUI X DataGrid Pro and AG Grid, offering advanced features for free.

👉 Live Demo & Docs — Interactive showcase with full API documentation

Upgrading from 2.x? 3.0.0 changes formatted text, exported files, callback indices and request ranges. See the v2 → v3 migration guide first: it starts with a table that maps what your app uses to the sections you need.


🛠️ Getting Started

Installation

npm install @opencorestack/opengridx
import { DataGrid } from '@opencorestack/opengridx';
import '@opencorestack/opengridx/styles';

Import the styles. The CSS ships as a separate file (dist/opengridx.css) and the JavaScript bundle does not load it. Import @opencorestack/opengridx/styles once, typically in your app's root file (main.tsx, App.tsx or Next.js layout.tsx). Without it the grid is unstyled and, because the viewport's scrolling comes from the stylesheet, it grows to fit every row and virtualization stops working. Development builds log a one-time [OpenGridX] The grid stylesheet is not loaded warning when this happens.


⚡ Basic Example

import { DataGrid, type GridColDef } from '@opencorestack/opengridx';
import '@opencorestack/opengridx/styles';

const columns: GridColDef[] = [
  { field: 'id',         headerName: 'ID',         width: 70 },
  { field: 'name',       headerName: 'Name',        width: 180 },
  { field: 'role',       headerName: 'Role',        width: 150 },
  { field: 'salary',     headerName: 'Salary',      width: 120, type: 'number',
    valueFormatter: ({ value }) => `$${(value as number).toLocaleString()}` },
  { field: 'department', headerName: 'Department',  width: 160 },
];

const rows = [
  { id: 1, name: 'Jon Snow',         role: 'Engineer',  salary: 95000,  department: 'Defense' },
  { id: 2, name: 'Cersei Lannister', role: 'Manager',   salary: 140000, department: 'Management' },
  { id: 3, name: 'Arya Stark',       role: 'Analyst',   salary: 65000,  department: 'Special Ops' },
];

export default function App() {
  return (
    <DataGrid
      rows={rows}
      columns={columns}
      checkboxSelection
      pagination
      height={400}
    />
  );
}

Without a height prop the grid fills its container (height: 100%, or the free space as a flex item), so put it in a container with a bounded height: a definite height, and min-height: 0 on every flex or grid item up to that element. In an unbounded container the grid grows to fit and every row is rendered (a development warning says so). See Virtualization.


🚀 Key Features

  • High-Performance Virtualization: Row and column virtualization for 100,000+ rows. (Browsers cap an element's height, so a single scrolling grid reaches about 645,000 rows at the default 52 px row height; page or stream larger datasets.)
  • Advanced Layouts: Native support for Row & Column Spanning, Grouping, and Tree Data.
  • Data Orchestration: 19 filter operators across string, number, date, boolean and select columns, AND/OR filter groups, multi-column sorting, and client or server pagination.
  • Zero UI Dependencies: 100% vanilla CSS (BEM) and pure React/TypeScript logic.
  • Skeleton Loader: Built-in animated skeleton rows that follow the current columns.
  • Fully Customizable: Slots system for replacing any component (pagination, overlays, toolbar).
  • Export Functionality: Built-in CSV, Excel (HTML .xls, or real .xlsx with the optional ExcelJS peer), JSON, PDF (optional jsPDF peer) and Print export.
  • Clipboard: Ctrl+C / Cmd+C copies the selected rows' visible columns as TSV for Excel/Sheets.
  • Accessibility: WCAG 2.1 AA — full ARIA roles and keyboard navigation.
  • Theming: CSS variable API, DataGridThemeProvider with 5 built-in themes (darkTheme, roseTheme, emeraldTheme, amberTheme, compactTheme) and custom themes.
  • Column & Row Reordering: Drag-and-drop column reordering and row reordering.
  • Inline Cell Editing: Full inline editing with processRowUpdate validation.
  • State Persistence: Save and restore grid state (sort, filter, pagination, columns, density) via useGridStateStorage.
  • AI-Native Integration: Shipped with raw lib/ source and docs/ inside the npm package, allowing AI agents (Cursor, Copilot, Windsurf) to flawlessly implement features by "seeing" the internal logic.

🤖 AI-Powered Implementation

OpenGridX is built for the era of AI-native development. When you install @opencorestack/opengridx, we include the full raw source code and markdown documentation in your node_modules.

This means that Cursor, GitHub Copilot, Windsurf, and other AI agents can read the actual implementation patterns and docs to accurately help you build complex features like server-side tree data or pivot tables without guessing.

Tip for Cursor/Copilot users: If your AI is struggling, tell it to "Read the docs and source in ./node_modules/@opencorestack/opengridx/docs" for instant context.


📐 API Reference

Core Props

PropTypeDefaultDescription
rowsGridRowModel[]—Required. Array of data rows. The grid never modifies or copies them.
columnsGridColDef[]—Required. Column definitions.
heightnumber | string—Grid height in pixels or a CSS string (e.g. '100%'). No default: fills its container's height (height: 100%, or flex: 1 with min-height: 0 as a flex item); in an auto-height container it grows to fit every row.
autoHeightbooleanfalseGrows the grid to fit all rows (renders every row).
loadingbooleanfalseWith no rows, shows skeleton rows (or slots.loadingOverlay). With rows shown, keeps them and runs a progress bar along the top (or shows slots.loadingOverlay over them). Works in list view too.
density'compact' | 'standard' | 'comfortable''standard'Row height preset: 32 px, rowHeight, 72 px.
overscanRowCountnumber3Minimum rows rendered outside the viewport; raised automatically while scrolling fast.
checkboxSelectionbooleanfalseEnables checkbox column for row selection.
paginationbooleanfalseEnables the pager. Ignored under row grouping and with paginationMode="infinite".
paginationModel{ page: number; pageSize: number }—Controlled pagination state (pair with onPaginationModelChange).
onPaginationModelChange(model) => void—Fires on page or page size change.
pageSizeOptionsnumber[][10, 25, 50, 100]Available page size options.
getRowId(row) => GridRowIdrow.idCustom row ID accessor.
rowHeightnumber52Row height in pixels.
headerHeightnumber56Header height in pixels.
noRowsLabelstring'No Data'Custom empty-state message.
localeTextGridLocaleText—Overrides for the pager and empty-state strings (i18n).
ariaLabelstring—Accessible name for the grid.
initialStateGridInitialState—Starting sort, filter, pagination, column and density state (uncontrolled).
onStateChange(state: GridState) => void—Fires when sort, filter, pagination, columns or density change.
apiRefMutableRefObject<GridApi<R>> | MutableRefObject<GridApi>—Imperative API; create it with useGridApiRef<R>() so the row getters return your row type (or useGridApiRef() for GridRowModel).
classNamestring—Custom CSS class on the grid container.
styleReact.CSSProperties—Custom inline styles on the grid container.

Column Definitions (GridColDef)

PropertyTypeDefaultDescription
fieldstring—Required. Must match the row object key.
headerNamestring—Header label text.
descriptionstring—Tooltip on header hover.
widthnumber | string—Width in px, or a percentage string such as '20%'. A column with neither width nor flex behaves like flex: 1.
flexnumber—Flex-grow weight — shares the space left after fixed and percentage columns. When set, width is ignored.
minWidthnumber—Minimum width in px (layout and resizing; resizing defaults to 50).
maxWidthnumber—Maximum width in px (layout and resizing).
align'left' | 'center' | 'right''left'Cell content alignment.
headerAlign'left' | 'center' | 'right''left'Header content alignment.
type'string' | 'number' | 'date' | 'boolean' | 'singleSelect' | 'image''string'Drives filter operators and, without a valueFormatter, the cell text: local date, Yes / No, singleSelect label, <img> for image.
valueOptionsArray<string | number | { value, label }>—Options list for type: 'singleSelect'.
editablebooleanfalseEnables inline cell editing. Pair with processRowUpdate.
valueGetter(params) => unknown—Derive a computed cell value from the row. Sorting, filtering, quick search, aggregation, copy and export use it.
valueSetter(params) => row—Maps an edited value back onto the row, for editable valueGetter columns.
valueFormatter(params) => string—Format the display string (does not affect edit or sort; quick search also matches it).
renderCell(params) => ReactNode—Fully custom cell renderer.
renderHeader(params) => ReactNode—Custom header cell renderer.
renderEditCell(params) => ReactNode—Custom editor rendered in edit mode.
cellClassNamestring | ((params) => string)—CSS class on every cell; use a function for conditional per-row styling.
headerClassNamestring—CSS class on the header cell.
groupingValueFormatter({ field, value }) => string—Label for group rows when grouping by this column (default "field: value").
colSpannumber | ((params) => number)—Merge cells horizontally.
rowSpannumber | ((params) => number)—Merge cells vertically.
sortablebooleantrueEnable/disable column sorting.
sortComparator(v1, v2, p1, p2) => number—Custom ascending comparator (sees nulls; negated for desc). v3.1.0+
filterablebooleantrueEnable/disable column filtering.
resizablebooleantrueAllow drag-resize; double-click the handle to auto-size to content (v3.1.0+).
hideablebooleantrueAllow hiding via the column panel.
pinnablebooleantrueAllow pinning via the UI.
disableColumnMenubooleanfalseHide the column header context menu.
exportablebooleantrueSet to false to exclude from all exports.
groupablebooleantrueSet to false to skip this column in rowGroupingModel and the pivot panel.
aggregableboolean—false keeps the column out of aggregation. The Summaries panel offers number columns, plus any column with aggregable: true.
availableAggregationFunctionsstring[]allRestrict which aggregation functions are offered.

Selection

PropTypeDescription
rowSelectionModelGridRowId[]Controlled selected row IDs.
onRowSelectionModelChange(model: GridRowId[]) => voidFires on selection change.
disableRowSelectionOnClickbooleanPrevent row click from toggling selection.
disableMultipleRowSelectionbooleanRestrict to single-row selection.
pinCheckboxColumnbooleanKeeps the checkbox column visible during horizontal scroll. Default true.
disableClipboardCopybooleanTurns off the grid's Ctrl/Cmd+C copy (apiRef.current.copySelectedRows() still works).

Sorting & Filtering

PropTypeDescription
sortModelGridSortItem[]Controlled sort model.
onSortModelChange(model: GridSortItem[]) => voidFires on sort change.
filterModelGridFilterModelControlled filter model.
onFilterModelChange(model: GridFilterModel) => voidFires on filter change.
multiSortbooleanEvery header click adds to the sort instead of replacing it (Shift+click always does).

Columns

PropTypeDescription
columnVisibilityModelGridColumnVisibilityModelControlled column visibility.
onColumnVisibilityModelChange(model) => voidFires when column visibility changes.
columnOrderstring[]Controlled column field order.
onColumnOrderChange(params) => voidFires when the user moves a column.
onColumnOrderModelChange(columnOrder) => voidFires with the whole new column order (also for the Columns panel's Reset).
disableColumnReorderbooleanDisables drag-and-drop column reordering.

Events

PropTypeDescription
onRowClick(params: GridRowParams) => voidFires when a row is clicked (or Enter is pressed on a non-editable cell).
onRowDoubleClick(params: GridRowParams) => voidFires when a row is double-clicked.
onCellClick(params: GridCellParams) => voidFires when a cell is clicked.
onRowsScrollEnd(params: GridRowScrollEndParams) => voidFires once each time scrolling reaches the bottom.

Server-Side

PropTypeDescription
paginationMode'client' | 'server' | 'infinite'Data fetching mode.
sortingMode'client' | 'server'Where sorting is applied.
filterMode'client' | 'server'Where filtering is applied.
dataSourceGridDataSourceServer-side data adapter.
rowCountnumberTotal rows for paginationMode="server" without a dataSource (with one, the response's rowCount wins).

Pinning

PropTypeDescription
pinnedColumnsGridColumnPinningPin columns to left or right.
onPinnedColumnsChange(model) => voidFires when column pinning changes.
pinnedRowsGridRowPinningPin rows (by id) to top or bottom. Ignored under tree data and row grouping.

Inline Editing

PropTypeDescription
isCellEditable(params: GridCellParams) => booleanPer-cell editability predicate.
processRowUpdate(newRow, oldRow) => R | Promise<R>Handles row save; supports async validation.
onProcessRowUpdateError(error: unknown) => voidFires if processRowUpdate throws, rejects or returns no row. The cell then stays in edit mode with the typed value, so the user can correct it or press Escape to cancel.

Row Reordering

PropTypeDescription
rowReorderingbooleanEnables drag-and-drop row reordering.
onRowOrderChange(params) => voidFires when a row is dropped; oldIndex / targetIndex are positions in the rows prop.

Advanced Features

PropTypeDescription
treeDatabooleanEnables hierarchical tree data display.
getTreeDataPath(row) => string[]Returns the path array for each row in tree mode.
groupingColDefPartial<GridColDef>With rowGroupingModel or treeData, adds a dedicated __group__ column, pinned left (field not needed).
defaultGroupingExpansionDepthnumberInitial expansion depth for tree data and row grouping. Default 0 (collapsed); -1 expands everything.
rowGroupingModelGridRowGroupingModelFields to group rows by (e.g. ['department']).
aggregationModelGridAggregationModelControlled aggregation state (e.g. { salary: 'sum' }).
onAggregationModelChange(model) => voidFires when aggregation changes.
getAggregationPosition(groupNode) => 'inline' | 'footer' | nullWhere group and grand totals appear.
pivotModebooleanSwitches the grid to multidimensional pivot mode.
pivotModelGridPivotModelControlled pivot configuration (rows, columns, values).
onPivotModelChange(model) => voidFires when pivot model changes.

Master-Detail

PropTypeDescription
getDetailPanelContent(params) => ReactNodeRenders the expandable detail panel.
getDetailPanelHeight(params) => number | 'auto'Detail panel height in px, or 'auto' to measure the content. Default 200.
detailPanelExpandedRowIdsSet<GridRowId>Controlled expanded rows.
onDetailPanelExpandedRowIdsChange(ids) => voidFires when expanded rows change.

Customization

PropTypeDescription
slotsGridSlotsReplace built-in components (toolbar, pagination, overlays, footer).
slotPropsobjectPass custom props to slot components.
listView / listViewColumnboolean / GridListViewColDefRender rows as cards (mobile layouts). Honours loading, overlays and slots.footer; keyboard navigable; tree/group parents get an expand chevron. Not virtualized: every row of the page is rendered, so use pagination or server-side loading for more than a few thousand rows (development builds warn above 2,000).
columnGroupingModelGridColumnGroupingModelMulti-level column group headers.

Per-cell styling uses GridColDef.cellClassName / headerClassName; themes are applied with <DataGridThemeProvider theme={…}> (see Theming below).

apiRef — Imperative API

const apiRef = useGridApiRef();
<DataGrid apiRef={apiRef} ... />

// Rows
apiRef.current.getRow(id)              // → GridRowModel | null
apiRef.current.getAllRows()            // → GridRowModel[]
apiRef.current.getVisibleRows()        // → GridRowModel[] (rows on screen: current page + pinned rows)
apiRef.current.getAllFilteredRows()    // → GridRowModel[] (every filtered + sorted row, all pages)
apiRef.current.getGroupedExportRows()  // → GridGroupedExportRow[] | null (row grouping only)

// Columns
apiRef.current.getColumn(field)        // → GridColDef | null
apiRef.current.getAllColumns()         // → GridColDef[]
apiRef.current.getVisibleColumns()     // → GridColDef[] (visible columns, display order)

// Selection
apiRef.current.selectRow(id, true)
apiRef.current.selectRows([id1, id2], true)
apiRef.current.getSelectedRows()       // → GridRowId[]

// Sorting
apiRef.current.sortColumn('name', 'asc')   // null removes the column's sort
apiRef.current.getSortModel()          // → GridSortItem[]

// Filtering
apiRef.current.setFilterModel(model)
apiRef.current.getFilterModel()        // → GridFilterModel

// Pagination
apiRef.current.setPage(2)
apiRef.current.setPageSize(50)

// Scroll
apiRef.current.scrollToIndexes({ rowIndex: 100, colIndex: 3 })
apiRef.current.autosizeColumn('name')   // fit to content (v3.1.0+)

// Clipboard
apiRef.current.copySelectedRows()      // → Promise<void>, rejects if the clipboard write fails

// Aggregation
apiRef.current.getAggregationResult()  // → Record<string, unknown> | null
apiRef.current.getAggregationModel()   // → GridAggregationModel | null

🎨 Customization & Extensibility

Slots System

Replace any built-in component with your own:

<DataGrid
  rows={rows}
  columns={columns}
  slots={{
    toolbar: CustomToolbar,
    pagination: CustomPaginationComponent,
    noRowsOverlay: CustomEmptyState,
    loadingOverlay: CustomLoader,
    footer: CustomFooter
  }}
  slotProps={{
    toolbar: { /* custom props */ },
    pagination: { /* custom props */ }
  }}
/>

Built-in Toolbar

import { DataGrid, GridToolbar } from '@opencorestack/opengridx';

<DataGrid
  rows={rows}
  columns={columns}
  filterModel={filterModel}
  onFilterModelChange={setFilterModel}
  columnVisibilityModel={columnVisibilityModel}
  onColumnVisibilityModelChange={setColumnVisibilityModel}
  slots={{ toolbar: GridToolbar }}
/>

GridToolbar provides global search, column visibility management, advanced filters, and aggregation (Summaries) controls, plus a Pivot button when pivot mode is configured. Mounted through slots.toolbar, every control works without any filter or visibility props: the grid keeps that state itself, and the props above only make it controlled. Rendered on its own, outside a grid, GridToolbar hides each button whose callback prop (onFilterModelChange, onColumnVisibilityModelChange, onAggregationModelChange, onPivotModelChange) is not passed.

Customizing individual toolbar controls

Replace any single control using render props — the panels remain fully functional:

PropWhat it replaces
renderQuickFilter(props)The search input. Receives { value, onChange }.
renderColumnsButton(props)The Columns toggle button. Receives { onClick, isOpen, activeCount }.
renderFilterButton(props)The Filters toggle button. Receives { onClick, isOpen, activeCount }.
renderAggregationButton(props)The Summaries toggle button. Receives { onClick, isOpen, activeCount }.
renderExportButton()Inserts an Export button slot (no built-in exists).
classNameCSS class on the toolbar root for theme overrides.
import { GridToolbar, exportToCsv, useGridApiRef,
         type ToolbarButtonRenderProps, type ToolbarQuickFilterRenderProps } from '@opencorestack/opengridx';

const apiRef = useGridApiRef();

<DataGrid
  rows={rows}
  columns={columns}
  apiRef={apiRef}
  slots={{ toolbar: GridToolbar }}
  slotProps={{
    toolbar: {
      className: 'my-branded-toolbar',
      renderQuickFilter: ({ value, onChange }: ToolbarQuickFilterRenderProps) => (
        <input className="my-search" value={value} onChange={e => onChange(e.target.value)} />
      ),
      renderFilterButton: ({ onClick, isOpen, activeCount }: ToolbarButtonRenderProps) => (
        <button className={isOpen ? 'active' : ''} onClick={onClick}>
          Filters {activeCount > 0 && <span>{activeCount}</span>}
        </button>
      ),
      renderExportButton: () => (
        <button onClick={() => exportToCsv(apiRef.current.getAllFilteredRows(), apiRef.current.getVisibleColumns())}>
          Export CSV
        </button>
      ),
    },
  }}
/>

Export Functionality

OpenGridX has two tiers of Excel export:

import {
    exportToCsv,
    exportToExcel,          // ✅ built-in, zero deps
    exportToExcelAdvanced,  // ✅ rich .xlsx — requires: npm install exceljs
    exportToJson,
    exportToPdf,            // ✅ PDF — requires: npm install jspdf jspdf-autotable
    printGrid
} from '@opencorestack/opengridx';

exportToCsv(rows, columns, { fileName: 'data.csv' });
exportToExcel(rows, columns, { fileName: 'data.xls' });  // HTML table, opened by Excel as .xls
exportToExcelAdvanced(rows, columns, {
    fileName: 'data.xlsx',
    columnStyles: { avatar: { embedImage: true, imageWidth: 40, imageHeight: 40 } }
});
exportToJson(rows, columns, { fileName: 'data.json' });
await exportToPdf(rows, columns, { fileName: 'data', title: 'Report Title' });
printGrid(rows, columns, 'Report Title');

Optional peer dependencies: exportToExcelAdvanced requires ExcelJS (npm install exceljs); exportToPdf requires npm install jspdf jspdf-autotable.

Large exports: PDF size and time grow fast (75–120 MB and several seconds for 50,000 grouped rows). Since v3.2 exportToPdf yields between slices so the page stays responsive, reports onProgress and accepts an abort signal; above maxRows (default 20,000) it warns in development. For more than a few thousand rows export CSV or .xlsx. PDF's built-in font only covers Latin-1: pass the font option (a Unicode .ttf) for characters such as ₹, otherwise they print as ?. See PDF Export.

CSV and HTML-Excel exports neutralise spreadsheet formulas (text starting with =, +, -, @, a tab or a carriage return gets a leading '; opt out with escapeFormulas: false), and CSV starts with a UTF-8 BOM (bom: false omits it). exportToExcel writes an HTML table, so a .xlsx file name is saved as .xls. To export what the grid shows, pass apiRef.current.getAllFilteredRows() and apiRef.current.getVisibleColumns(); with a custom getRowId, also pass getRowId in the options so selectedRows match. See the Export Guide.

State Persistence

import { useGridStateStorage } from '@opencorestack/opengridx';

const { initialState, onStateChange, clearState } = useGridStateStorage('my-grid-key');

<DataGrid
  rows={rows}
  columns={columns}
  initialState={initialState}
  onStateChange={onStateChange}
/>

Theming

import { DataGridThemeProvider, darkTheme } from '@opencorestack/opengridx';

<DataGridThemeProvider theme={darkTheme}>
  <DataGrid rows={rows} columns={columns} />
</DataGridThemeProvider>

Built-in themes: darkTheme, roseTheme, emeraldTheme, amberTheme, compactTheme. The provider pins a complete light or dark palette (theme.mode), whatever the operating system's colour scheme.


⚡ Performance & Bundle Size

ArtifactSizeGzippedNotes
Core ES Module (opengridx.es.js)331 KB86 KBUse this — tree-shakeable
Core UMD (opengridx.umd.js)233 KB73 KBCommonJS / CDN compat
Styles (opengridx.css)65 KB10 KBimport '@opencorestack/opengridx/styles'
ExcelJS (optional peer dep)——npm install exceljs
jsPDF (optional peer dep)——npm install jspdf jspdf-autotable
npm package download—~1.1 MBCompressed tarball (includes lib/ source, docs and source maps)
  • Tree-shaking Ready: ES Module build — bundlers (Vite, Webpack) only include what you use.
  • Zero UI Dependencies: No MUI, Ant Design, or Radix. Pure React + vanilla CSS.
  • Lazy Advanced Export: ExcelJS and jsPDF are optional peer deps — not bundled, loaded only when you call exportToExcelAdvanced / exportToPdf.
  • Efficient Rendering: Row and column virtualization keeps the DOM small for 100k+ rows (needs a bounded grid height).

📚 Documentation

Full documentation at 👉 opencorestack.github.io/OpenGridX · 📖 Wiki

🏛️ Components

🚀 Features

📊 Advanced Data

🎨 Customization


🚀 Why OpenGridX?

Most React grids gatekeep essential features like Row Grouping, Excel Export, and Master-Detail behind expensive annual licenses. OpenGridX provides these premium capabilities out-of-the-box, with full source-code control and no external UI dependencies.

FeatureMUI FreeMUI Pro ($$$)AG Grid CommunityOpenGridX
Virtualization✅✅✅✅
Column Pinning❌✅✅✅
Row Grouping❌✅✅✅
Tree Data❌✅✅✅
Master-Detail❌✅✅✅
Excel Export❌✅❌✅
Advanced Filtering❌✅❌✅
Aggregation❌✅❌✅
Pivot Mode❌✅❌✅
Inline Cell Editing❌✅✅✅
State Persistence❌✅❌✅
PriceFree$$$Free (limited)Free

🤝 Contributing

See CONTRIBUTING.md and the Code of Conduct. Bugs and feature requests: open an issue.

🧪 Testing

npm test                # unit tests (jsdom)
npm run test:browser    # real Chromium, Firefox and WebKit
npm run test:smoke      # pack the library, install it into React 18/19 apps, run Playwright

Which layer to use, and how the package smoke suite works: docs/contributing/testing.md.


📝 License

MIT © 2026 Open Core Stack

datagrid
data-table
enterpise-grade
export-excel
free
react
react-datagrid
react-datatable
react-table
row-grouping
tree-data
typescript
virtualization
zero-dependency

opencorestack/opengridx

High-performance React data grid infrastructure. Unlock advanced Row Grouping, Excel Export, and Column Pinning without the usual "Pro" gatekeeping. Built for speed, scale, and complete architectural freedom. Fully open, virtualization-ready, and feature-complete.

TypeScript

2

27 commits

updated Oct 1, 2026

See the code

See what people are saying

SourceMessageScoreDate

Tired of "Enterprise" paywalls on React Data Grids, so I built OpenGridX (MIT) (r/reactjs)

​Hey everyone, ​Every React dev has been here: you pick a popular data grid library for an internal tool or client dashboard. Everything works great until a product manager asks for column pinning, row grouping, or exporting to Excel. ​Suddenly, your options are: ​Explain to management why they…

0

Oct 1, 2026

README

OpenGridX | The Free Enterprise React DataGrid

OpenGridX Hero Banner

OpenGridX is a premium, high-performance React DataGrid engine designed to break the "Pay-to-Play" model in the React ecosystem. It provides a pure-custom, zero-dependency alternative to enterprise grids like MUI X DataGrid Pro and AG Grid, offering advanced features for free.

👉 Live Demo & Docs — Interactive showcase with full API documentation

Upgrading from 2.x? 3.0.0 changes formatted text, exported files, callback indices and request ranges. See the v2 → v3 migration guide first: it starts with a table that maps what your app uses to the sections you need.


🛠️ Getting Started

Installation

npm install @opencorestack/opengridx
import { DataGrid } from '@opencorestack/opengridx';
import '@opencorestack/opengridx/styles';

Import the styles. The CSS ships as a separate file (dist/opengridx.css) and the JavaScript bundle does not load it. Import @opencorestack/opengridx/styles once, typically in your app's root file (main.tsx, App.tsx or Next.js layout.tsx). Without it the grid is unstyled and, because the viewport's scrolling comes from the stylesheet, it grows to fit every row and virtualization stops working. Development builds log a one-time [OpenGridX] The grid stylesheet is not loaded warning when this happens.


⚡ Basic Example

import { DataGrid, type GridColDef } from '@opencorestack/opengridx';
import '@opencorestack/opengridx/styles';

const columns: GridColDef[] = [
  { field: 'id',         headerName: 'ID',         width: 70 },
  { field: 'name',       headerName: 'Name',        width: 180 },
  { field: 'role',       headerName: 'Role',        width: 150 },
  { field: 'salary',     headerName: 'Salary',      width: 120, type: 'number',
    valueFormatter: ({ value }) => `$${(value as number).toLocaleString()}` },
  { field: 'department', headerName: 'Department',  width: 160 },
];

const rows = [
  { id: 1, name: 'Jon Snow',         role: 'Engineer',  salary: 95000,  department: 'Defense' },
  { id: 2, name: 'Cersei Lannister', role: 'Manager',   salary: 140000, department: 'Management' },
  { id: 3, name: 'Arya Stark',       role: 'Analyst',   salary: 65000,  department: 'Special Ops' },
];

export default function App() {
  return (
    <DataGrid
      rows={rows}
      columns={columns}
      checkboxSelection
      pagination
      height={400}
    />
  );
}

Without a height prop the grid fills its container (height: 100%, or the free space as a flex item), so put it in a container with a bounded height: a definite height, and min-height: 0 on every flex or grid item up to that element. In an unbounded container the grid grows to fit and every row is rendered (a development warning says so). See Virtualization.


🚀 Key Features

  • High-Performance Virtualization: Row and column virtualization for 100,000+ rows. (Browsers cap an element's height, so a single scrolling grid reaches about 645,000 rows at the default 52 px row height; page or stream larger datasets.)
  • Advanced Layouts: Native support for Row & Column Spanning, Grouping, and Tree Data.
  • Data Orchestration: 19 filter operators across string, number, date, boolean and select columns, AND/OR filter groups, multi-column sorting, and client or server pagination.
  • Zero UI Dependencies: 100% vanilla CSS (BEM) and pure React/TypeScript logic.
  • Skeleton Loader: Built-in animated skeleton rows that follow the current columns.
  • Fully Customizable: Slots system for replacing any component (pagination, overlays, toolbar).
  • Export Functionality: Built-in CSV, Excel (HTML .xls, or real .xlsx with the optional ExcelJS peer), JSON, PDF (optional jsPDF peer) and Print export.
  • Clipboard: Ctrl+C / Cmd+C copies the selected rows' visible columns as TSV for Excel/Sheets.
  • Accessibility: WCAG 2.1 AA — full ARIA roles and keyboard navigation.
  • Theming: CSS variable API, DataGridThemeProvider with 5 built-in themes (darkTheme, roseTheme, emeraldTheme, amberTheme, compactTheme) and custom themes.
  • Column & Row Reordering: Drag-and-drop column reordering and row reordering.
  • Inline Cell Editing: Full inline editing with processRowUpdate validation.
  • State Persistence: Save and restore grid state (sort, filter, pagination, columns, density) via useGridStateStorage.
  • AI-Native Integration: Shipped with raw lib/ source and docs/ inside the npm package, allowing AI agents (Cursor, Copilot, Windsurf) to flawlessly implement features by "seeing" the internal logic.

🤖 AI-Powered Implementation

OpenGridX is built for the era of AI-native development. When you install @opencorestack/opengridx, we include the full raw source code and markdown documentation in your node_modules.

This means that Cursor, GitHub Copilot, Windsurf, and other AI agents can read the actual implementation patterns and docs to accurately help you build complex features like server-side tree data or pivot tables without guessing.

Tip for Cursor/Copilot users: If your AI is struggling, tell it to "Read the docs and source in ./node_modules/@opencorestack/opengridx/docs" for instant context.


📐 API Reference

Core Props

PropTypeDefaultDescription
rowsGridRowModel[]—Required. Array of data rows. The grid never modifies or copies them.
columnsGridColDef[]—Required. Column definitions.
heightnumber | string—Grid height in pixels or a CSS string (e.g. '100%'). No default: fills its container's height (height: 100%, or flex: 1 with min-height: 0 as a flex item); in an auto-height container it grows to fit every row.
autoHeightbooleanfalseGrows the grid to fit all rows (renders every row).
loadingbooleanfalseWith no rows, shows skeleton rows (or slots.loadingOverlay). With rows shown, keeps them and runs a progress bar along the top (or shows slots.loadingOverlay over them). Works in list view too.
density'compact' | 'standard' | 'comfortable''standard'Row height preset: 32 px, rowHeight, 72 px.
overscanRowCountnumber3Minimum rows rendered outside the viewport; raised automatically while scrolling fast.
checkboxSelectionbooleanfalseEnables checkbox column for row selection.
paginationbooleanfalseEnables the pager. Ignored under row grouping and with paginationMode="infinite".
paginationModel{ page: number; pageSize: number }—Controlled pagination state (pair with onPaginationModelChange).
onPaginationModelChange(model) => void—Fires on page or page size change.
pageSizeOptionsnumber[][10, 25, 50, 100]Available page size options.
getRowId(row) => GridRowIdrow.idCustom row ID accessor.
rowHeightnumber52Row height in pixels.
headerHeightnumber56Header height in pixels.
noRowsLabelstring'No Data'Custom empty-state message.
localeTextGridLocaleText—Overrides for the pager and empty-state strings (i18n).
ariaLabelstring—Accessible name for the grid.
initialStateGridInitialState—Starting sort, filter, pagination, column and density state (uncontrolled).
onStateChange(state: GridState) => void—Fires when sort, filter, pagination, columns or density change.
apiRefMutableRefObject<GridApi<R>> | MutableRefObject<GridApi>—Imperative API; create it with useGridApiRef<R>() so the row getters return your row type (or useGridApiRef() for GridRowModel).
classNamestring—Custom CSS class on the grid container.
styleReact.CSSProperties—Custom inline styles on the grid container.

Column Definitions (GridColDef)

PropertyTypeDefaultDescription
fieldstring—Required. Must match the row object key.
headerNamestring—Header label text.
descriptionstring—Tooltip on header hover.
widthnumber | string—Width in px, or a percentage string such as '20%'. A column with neither width nor flex behaves like flex: 1.
flexnumber—Flex-grow weight — shares the space left after fixed and percentage columns. When set, width is ignored.
minWidthnumber—Minimum width in px (layout and resizing; resizing defaults to 50).
maxWidthnumber—Maximum width in px (layout and resizing).
align'left' | 'center' | 'right''left'Cell content alignment.
headerAlign'left' | 'center' | 'right''left'Header content alignment.
type'string' | 'number' | 'date' | 'boolean' | 'singleSelect' | 'image''string'Drives filter operators and, without a valueFormatter, the cell text: local date, Yes / No, singleSelect label, <img> for image.
valueOptionsArray<string | number | { value, label }>—Options list for type: 'singleSelect'.
editablebooleanfalseEnables inline cell editing. Pair with processRowUpdate.
valueGetter(params) => unknown—Derive a computed cell value from the row. Sorting, filtering, quick search, aggregation, copy and export use it.
valueSetter(params) => row—Maps an edited value back onto the row, for editable valueGetter columns.
valueFormatter(params) => string—Format the display string (does not affect edit or sort; quick search also matches it).
renderCell(params) => ReactNode—Fully custom cell renderer.
renderHeader(params) => ReactNode—Custom header cell renderer.
renderEditCell(params) => ReactNode—Custom editor rendered in edit mode.
cellClassNamestring | ((params) => string)—CSS class on every cell; use a function for conditional per-row styling.
headerClassNamestring—CSS class on the header cell.
groupingValueFormatter({ field, value }) => string—Label for group rows when grouping by this column (default "field: value").
colSpannumber | ((params) => number)—Merge cells horizontally.
rowSpannumber | ((params) => number)—Merge cells vertically.
sortablebooleantrueEnable/disable column sorting.
sortComparator(v1, v2, p1, p2) => number—Custom ascending comparator (sees nulls; negated for desc). v3.1.0+
filterablebooleantrueEnable/disable column filtering.
resizablebooleantrueAllow drag-resize; double-click the handle to auto-size to content (v3.1.0+).
hideablebooleantrueAllow hiding via the column panel.
pinnablebooleantrueAllow pinning via the UI.
disableColumnMenubooleanfalseHide the column header context menu.
exportablebooleantrueSet to false to exclude from all exports.
groupablebooleantrueSet to false to skip this column in rowGroupingModel and the pivot panel.
aggregableboolean—false keeps the column out of aggregation. The Summaries panel offers number columns, plus any column with aggregable: true.
availableAggregationFunctionsstring[]allRestrict which aggregation functions are offered.

Selection

PropTypeDescription
rowSelectionModelGridRowId[]Controlled selected row IDs.
onRowSelectionModelChange(model: GridRowId[]) => voidFires on selection change.
disableRowSelectionOnClickbooleanPrevent row click from toggling selection.
disableMultipleRowSelectionbooleanRestrict to single-row selection.
pinCheckboxColumnbooleanKeeps the checkbox column visible during horizontal scroll. Default true.
disableClipboardCopybooleanTurns off the grid's Ctrl/Cmd+C copy (apiRef.current.copySelectedRows() still works).

Sorting & Filtering

PropTypeDescription
sortModelGridSortItem[]Controlled sort model.
onSortModelChange(model: GridSortItem[]) => voidFires on sort change.
filterModelGridFilterModelControlled filter model.
onFilterModelChange(model: GridFilterModel) => voidFires on filter change.
multiSortbooleanEvery header click adds to the sort instead of replacing it (Shift+click always does).

Columns

PropTypeDescription
columnVisibilityModelGridColumnVisibilityModelControlled column visibility.
onColumnVisibilityModelChange(model) => voidFires when column visibility changes.
columnOrderstring[]Controlled column field order.
onColumnOrderChange(params) => voidFires when the user moves a column.
onColumnOrderModelChange(columnOrder) => voidFires with the whole new column order (also for the Columns panel's Reset).
disableColumnReorderbooleanDisables drag-and-drop column reordering.

Events

PropTypeDescription
onRowClick(params: GridRowParams) => voidFires when a row is clicked (or Enter is pressed on a non-editable cell).
onRowDoubleClick(params: GridRowParams) => voidFires when a row is double-clicked.
onCellClick(params: GridCellParams) => voidFires when a cell is clicked.
onRowsScrollEnd(params: GridRowScrollEndParams) => voidFires once each time scrolling reaches the bottom.

Server-Side

PropTypeDescription
paginationMode'client' | 'server' | 'infinite'Data fetching mode.
sortingMode'client' | 'server'Where sorting is applied.
filterMode'client' | 'server'Where filtering is applied.
dataSourceGridDataSourceServer-side data adapter.
rowCountnumberTotal rows for paginationMode="server" without a dataSource (with one, the response's rowCount wins).

Pinning

PropTypeDescription
pinnedColumnsGridColumnPinningPin columns to left or right.
onPinnedColumnsChange(model) => voidFires when column pinning changes.
pinnedRowsGridRowPinningPin rows (by id) to top or bottom. Ignored under tree data and row grouping.

Inline Editing

PropTypeDescription
isCellEditable(params: GridCellParams) => booleanPer-cell editability predicate.
processRowUpdate(newRow, oldRow) => R | Promise<R>Handles row save; supports async validation.
onProcessRowUpdateError(error: unknown) => voidFires if processRowUpdate throws, rejects or returns no row. The cell then stays in edit mode with the typed value, so the user can correct it or press Escape to cancel.

Row Reordering

PropTypeDescription
rowReorderingbooleanEnables drag-and-drop row reordering.
onRowOrderChange(params) => voidFires when a row is dropped; oldIndex / targetIndex are positions in the rows prop.

Advanced Features

PropTypeDescription
treeDatabooleanEnables hierarchical tree data display.
getTreeDataPath(row) => string[]Returns the path array for each row in tree mode.
groupingColDefPartial<GridColDef>With rowGroupingModel or treeData, adds a dedicated __group__ column, pinned left (field not needed).
defaultGroupingExpansionDepthnumberInitial expansion depth for tree data and row grouping. Default 0 (collapsed); -1 expands everything.
rowGroupingModelGridRowGroupingModelFields to group rows by (e.g. ['department']).
aggregationModelGridAggregationModelControlled aggregation state (e.g. { salary: 'sum' }).
onAggregationModelChange(model) => voidFires when aggregation changes.
getAggregationPosition(groupNode) => 'inline' | 'footer' | nullWhere group and grand totals appear.
pivotModebooleanSwitches the grid to multidimensional pivot mode.
pivotModelGridPivotModelControlled pivot configuration (rows, columns, values).
onPivotModelChange(model) => voidFires when pivot model changes.

Master-Detail

PropTypeDescription
getDetailPanelContent(params) => ReactNodeRenders the expandable detail panel.
getDetailPanelHeight(params) => number | 'auto'Detail panel height in px, or 'auto' to measure the content. Default 200.
detailPanelExpandedRowIdsSet<GridRowId>Controlled expanded rows.
onDetailPanelExpandedRowIdsChange(ids) => voidFires when expanded rows change.

Customization

PropTypeDescription
slotsGridSlotsReplace built-in components (toolbar, pagination, overlays, footer).
slotPropsobjectPass custom props to slot components.
listView / listViewColumnboolean / GridListViewColDefRender rows as cards (mobile layouts). Honours loading, overlays and slots.footer; keyboard navigable; tree/group parents get an expand chevron. Not virtualized: every row of the page is rendered, so use pagination or server-side loading for more than a few thousand rows (development builds warn above 2,000).
columnGroupingModelGridColumnGroupingModelMulti-level column group headers.

Per-cell styling uses GridColDef.cellClassName / headerClassName; themes are applied with <DataGridThemeProvider theme={…}> (see Theming below).

apiRef — Imperative API

const apiRef = useGridApiRef();
<DataGrid apiRef={apiRef} ... />

// Rows
apiRef.current.getRow(id)              // → GridRowModel | null
apiRef.current.getAllRows()            // → GridRowModel[]
apiRef.current.getVisibleRows()        // → GridRowModel[] (rows on screen: current page + pinned rows)
apiRef.current.getAllFilteredRows()    // → GridRowModel[] (every filtered + sorted row, all pages)
apiRef.current.getGroupedExportRows()  // → GridGroupedExportRow[] | null (row grouping only)

// Columns
apiRef.current.getColumn(field)        // → GridColDef | null
apiRef.current.getAllColumns()         // → GridColDef[]
apiRef.current.getVisibleColumns()     // → GridColDef[] (visible columns, display order)

// Selection
apiRef.current.selectRow(id, true)
apiRef.current.selectRows([id1, id2], true)
apiRef.current.getSelectedRows()       // → GridRowId[]

// Sorting
apiRef.current.sortColumn('name', 'asc')   // null removes the column's sort
apiRef.current.getSortModel()          // → GridSortItem[]

// Filtering
apiRef.current.setFilterModel(model)
apiRef.current.getFilterModel()        // → GridFilterModel

// Pagination
apiRef.current.setPage(2)
apiRef.current.setPageSize(50)

// Scroll
apiRef.current.scrollToIndexes({ rowIndex: 100, colIndex: 3 })
apiRef.current.autosizeColumn('name')   // fit to content (v3.1.0+)

// Clipboard
apiRef.current.copySelectedRows()      // → Promise<void>, rejects if the clipboard write fails

// Aggregation
apiRef.current.getAggregationResult()  // → Record<string, unknown> | null
apiRef.current.getAggregationModel()   // → GridAggregationModel | null

🎨 Customization & Extensibility

Slots System

Replace any built-in component with your own:

<DataGrid
  rows={rows}
  columns={columns}
  slots={{
    toolbar: CustomToolbar,
    pagination: CustomPaginationComponent,
    noRowsOverlay: CustomEmptyState,
    loadingOverlay: CustomLoader,
    footer: CustomFooter
  }}
  slotProps={{
    toolbar: { /* custom props */ },
    pagination: { /* custom props */ }
  }}
/>

Built-in Toolbar

import { DataGrid, GridToolbar } from '@opencorestack/opengridx';

<DataGrid
  rows={rows}
  columns={columns}
  filterModel={filterModel}
  onFilterModelChange={setFilterModel}
  columnVisibilityModel={columnVisibilityModel}
  onColumnVisibilityModelChange={setColumnVisibilityModel}
  slots={{ toolbar: GridToolbar }}
/>

GridToolbar provides global search, column visibility management, advanced filters, and aggregation (Summaries) controls, plus a Pivot button when pivot mode is configured. Mounted through slots.toolbar, every control works without any filter or visibility props: the grid keeps that state itself, and the props above only make it controlled. Rendered on its own, outside a grid, GridToolbar hides each button whose callback prop (onFilterModelChange, onColumnVisibilityModelChange, onAggregationModelChange, onPivotModelChange) is not passed.

Customizing individual toolbar controls

Replace any single control using render props — the panels remain fully functional:

PropWhat it replaces
renderQuickFilter(props)The search input. Receives { value, onChange }.
renderColumnsButton(props)The Columns toggle button. Receives { onClick, isOpen, activeCount }.
renderFilterButton(props)The Filters toggle button. Receives { onClick, isOpen, activeCount }.
renderAggregationButton(props)The Summaries toggle button. Receives { onClick, isOpen, activeCount }.
renderExportButton()Inserts an Export button slot (no built-in exists).
classNameCSS class on the toolbar root for theme overrides.
import { GridToolbar, exportToCsv, useGridApiRef,
         type ToolbarButtonRenderProps, type ToolbarQuickFilterRenderProps } from '@opencorestack/opengridx';

const apiRef = useGridApiRef();

<DataGrid
  rows={rows}
  columns={columns}
  apiRef={apiRef}
  slots={{ toolbar: GridToolbar }}
  slotProps={{
    toolbar: {
      className: 'my-branded-toolbar',
      renderQuickFilter: ({ value, onChange }: ToolbarQuickFilterRenderProps) => (
        <input className="my-search" value={value} onChange={e => onChange(e.target.value)} />
      ),
      renderFilterButton: ({ onClick, isOpen, activeCount }: ToolbarButtonRenderProps) => (
        <button className={isOpen ? 'active' : ''} onClick={onClick}>
          Filters {activeCount > 0 && <span>{activeCount}</span>}
        </button>
      ),
      renderExportButton: () => (
        <button onClick={() => exportToCsv(apiRef.current.getAllFilteredRows(), apiRef.current.getVisibleColumns())}>
          Export CSV
        </button>
      ),
    },
  }}
/>

Export Functionality

OpenGridX has two tiers of Excel export:

import {
    exportToCsv,
    exportToExcel,          // ✅ built-in, zero deps
    exportToExcelAdvanced,  // ✅ rich .xlsx — requires: npm install exceljs
    exportToJson,
    exportToPdf,            // ✅ PDF — requires: npm install jspdf jspdf-autotable
    printGrid
} from '@opencorestack/opengridx';

exportToCsv(rows, columns, { fileName: 'data.csv' });
exportToExcel(rows, columns, { fileName: 'data.xls' });  // HTML table, opened by Excel as .xls
exportToExcelAdvanced(rows, columns, {
    fileName: 'data.xlsx',
    columnStyles: { avatar: { embedImage: true, imageWidth: 40, imageHeight: 40 } }
});
exportToJson(rows, columns, { fileName: 'data.json' });
await exportToPdf(rows, columns, { fileName: 'data', title: 'Report Title' });
printGrid(rows, columns, 'Report Title');

Optional peer dependencies: exportToExcelAdvanced requires ExcelJS (npm install exceljs); exportToPdf requires npm install jspdf jspdf-autotable.

Large exports: PDF size and time grow fast (75–120 MB and several seconds for 50,000 grouped rows). Since v3.2 exportToPdf yields between slices so the page stays responsive, reports onProgress and accepts an abort signal; above maxRows (default 20,000) it warns in development. For more than a few thousand rows export CSV or .xlsx. PDF's built-in font only covers Latin-1: pass the font option (a Unicode .ttf) for characters such as ₹, otherwise they print as ?. See PDF Export.

CSV and HTML-Excel exports neutralise spreadsheet formulas (text starting with =, +, -, @, a tab or a carriage return gets a leading '; opt out with escapeFormulas: false), and CSV starts with a UTF-8 BOM (bom: false omits it). exportToExcel writes an HTML table, so a .xlsx file name is saved as .xls. To export what the grid shows, pass apiRef.current.getAllFilteredRows() and apiRef.current.getVisibleColumns(); with a custom getRowId, also pass getRowId in the options so selectedRows match. See the Export Guide.

State Persistence

import { useGridStateStorage } from '@opencorestack/opengridx';

const { initialState, onStateChange, clearState } = useGridStateStorage('my-grid-key');

<DataGrid
  rows={rows}
  columns={columns}
  initialState={initialState}
  onStateChange={onStateChange}
/>

Theming

import { DataGridThemeProvider, darkTheme } from '@opencorestack/opengridx';

<DataGridThemeProvider theme={darkTheme}>
  <DataGrid rows={rows} columns={columns} />
</DataGridThemeProvider>

Built-in themes: darkTheme, roseTheme, emeraldTheme, amberTheme, compactTheme. The provider pins a complete light or dark palette (theme.mode), whatever the operating system's colour scheme.


⚡ Performance & Bundle Size

ArtifactSizeGzippedNotes
Core ES Module (opengridx.es.js)331 KB86 KBUse this — tree-shakeable
Core UMD (opengridx.umd.js)233 KB73 KBCommonJS / CDN compat
Styles (opengridx.css)65 KB10 KBimport '@opencorestack/opengridx/styles'
ExcelJS (optional peer dep)——npm install exceljs
jsPDF (optional peer dep)——npm install jspdf jspdf-autotable
npm package download—~1.1 MBCompressed tarball (includes lib/ source, docs and source maps)
  • Tree-shaking Ready: ES Module build — bundlers (Vite, Webpack) only include what you use.
  • Zero UI Dependencies: No MUI, Ant Design, or Radix. Pure React + vanilla CSS.
  • Lazy Advanced Export: ExcelJS and jsPDF are optional peer deps — not bundled, loaded only when you call exportToExcelAdvanced / exportToPdf.
  • Efficient Rendering: Row and column virtualization keeps the DOM small for 100k+ rows (needs a bounded grid height).

📚 Documentation

Full documentation at 👉 opencorestack.github.io/OpenGridX · 📖 Wiki

🏛️ Components

🚀 Features

📊 Advanced Data

🎨 Customization


🚀 Why OpenGridX?

Most React grids gatekeep essential features like Row Grouping, Excel Export, and Master-Detail behind expensive annual licenses. OpenGridX provides these premium capabilities out-of-the-box, with full source-code control and no external UI dependencies.

FeatureMUI FreeMUI Pro ($$$)AG Grid CommunityOpenGridX
Virtualization✅✅✅✅
Column Pinning❌✅✅✅
Row Grouping❌✅✅✅
Tree Data❌✅✅✅
Master-Detail❌✅✅✅
Excel Export❌✅❌✅
Advanced Filtering❌✅❌✅
Aggregation❌✅❌✅
Pivot Mode❌✅❌✅
Inline Cell Editing❌✅✅✅
State Persistence❌✅❌✅
PriceFree$$$Free (limited)Free

🤝 Contributing

See CONTRIBUTING.md and the Code of Conduct. Bugs and feature requests: open an issue.

🧪 Testing

npm test                # unit tests (jsdom)
npm run test:browser    # real Chromium, Firefox and WebKit
npm run test:smoke      # pack the library, install it into React 18/19 apps, run Playwright

Which layer to use, and how the package smoke suite works: docs/contributing/testing.md.


📝 License

MIT © 2026 Open Core Stack

datagrid
data-table
enterpise-grade
export-excel
free
react
react-datagrid
react-datatable
react-table
row-grouping
tree-data
typescript
virtualization
zero-dependency

Languages

TypeScript

94.8%

CSS

4.2%

JavaScript

1.0%