A feature-rich, headless-powered data grid for React — built on TanStack Table v8 and MUI. Sorting, filtering, pagination, row selection, column/row pinning, resizing, drag-and-drop column ordering, expandable rows, and split (frozen) columns — all out of the box.
Live Demo: https://munza-mui-data-grid.jsdevs.xyz/
- Global search + per-column filtering (text, range)
- Multi-column sorting
- Client-side & manual (server-side) pagination
- Row selection with checkboxes
- Column pinning (left / right) and row pinning (top / bottom)
- Resizable & drag-to-reorder columns (
@dnd-kit) - Split view for frozen left/right columns
- Expandable rows with custom sub-components
- Persists per-grid layout (column order, sizing, visibility, pinning) via a storage key
- Built-in loading, error, and empty states
- Fully styled with MUI — themeable out of the box
- Bring your own toolbar actions via
topRightSlot
npm install munza-mui-data-gridMake sure the following are installed in your project:
npm install react react-dom @mui/material @emotion/react @emotion/styled @mui/icons-materialimport { Grid, useGridState, type ColumnDef } from 'munza-mui-data-grid';
interface User {
id: string;
name: string;
email: string;
}
const columns: ColumnDef<User>[] = [
{ id: 'id', accessorKey: 'id', header: () => 'ID' },
{ id: 'name', accessorKey: 'name', header: () => 'Name' },
{ id: 'email', accessorKey: 'email', header: () => 'Email' },
];
const App = () => {
const { state, handlers } = useGridState();
return (
<Grid
payload={{ data: users, total: users.length }}
columns={columns}
state={state}
{...handlers}
/>
);
};const { state, handlers, rowSelection } = useGridState();
const { data, isLoading, isError, refetch, isFetching } = useUsersQuery({
queryString: URLSearch(state),
});
<Grid
payload={{ data: data?.rows ?? [], total: data?.total ?? 0 }}
columns={columns}
manualPagination
isLoading={isLoading}
isError={isError}
isFetching={isFetching}
refetch={refetch}
state={state}
{...handlers}
height="55vh"
/>;Add custom action buttons to the toolbar via topRightSlot:
import AddIcon from '@mui/icons-material/Add';
import RefreshIcon from '@mui/icons-material/Refresh';
import IconButton from '@mui/material/IconButton';
import Stack from '@mui/material/Stack';
import Tooltip from '@mui/material/Tooltip';
<Grid
columns={columns}
payload={{ data, total }}
state={state}
{...handlers}
topRightSlot={
<Stack direction="row" spacing={1}>
<Tooltip title="Refresh">
<IconButton size="small" onClick={refetch}>
<RefreshIcon fontSize="small" />
</IconButton>
</Tooltip>
<Tooltip title="Add">
<IconButton size="small" color="primary" onClick={handleAdd}>
<AddIcon fontSize="small" />
</IconButton>
</Tooltip>
</Stack>
}
/>;Use the pluckSelected utility to extract a field from all selected rows:
import { pluckSelected } from 'munza-mui-data-grid';
const { rowSelection } = useGridState();
const selectedIds = pluckSelected(data, rowSelection, 'id');
// => ["V001", "V003", "V004"]| Prop | Type | Default | Description |
|---|---|---|---|
payload |
{ data: T[]; total: number } |
— | Row data and total row count |
columns |
ColumnDef<T>[] |
— | Column definitions (required) |
state |
Partial<TableState> |
— | Controlled table state (from useGridState) |
onColumnFiltersChange |
OnChangeFn<ColumnFiltersState> |
— | Column filter change handler |
onPaginationChange |
OnChangeFn<PaginationState> |
— | Pagination change handler |
onSortingChange |
OnChangeFn<SortingState> |
— | Sorting change handler |
onRowSelectionChange |
OnChangeFn<RowSelectionState> |
— | Row selection change handler |
setGlobalFilter |
Dispatch<SetStateAction<string>> |
— | Global search setter |
manualPagination |
boolean |
false |
Enables server-side pagination |
isLoading |
boolean |
— | Shows skeleton loading rows |
isError |
boolean |
— | Shows the error state |
isFetching |
boolean |
— | Shows a background refetch indicator |
refetch |
() => void |
— | Retry/refresh callback |
renderSubComponent |
(props: { row: Row<T> }) => ReactElement |
— | Custom content for expanded rows |
getRowCanExpand |
(row: Row<T>) => boolean |
— | Controls whether a row can expand |
enableRowSelection |
boolean |
true |
Enables/disables row selection |
height |
string |
'65vh' |
Fixed height of the scrollable table body |
isToolbar |
boolean |
true |
Shows/hides the built-in toolbar |
isPagination |
boolean |
true |
Shows/hides the built-in pagination controls |
name |
string |
'munza' |
Storage key for persisting per-grid layout |
topRightSlot |
React.ReactNode |
— | Custom content on the right of the toolbar (e.g. action buttons) |
children |
React.ReactNode |
— | Extra content rendered above the table (e.g. bulk-action bars) |
Manages all controlled state required by <Grid />.
const { state, handlers, rowSelection } = useGridState();Returns:
state—{ columnFilters, globalFilter, pagination, sorting, rowSelection }handlers—{ onColumnFiltersChange, onPaginationChange, onSortingChange, setGlobalFilter, onRowSelectionChange }rowSelection— the current selection map, exposed directly for convenience
Extracts a field's value from every currently selected row.
pluckSelected(data, rowSelection, 'id'); // => string[]Serializes TanStack Table state into a MongoDB/Express-style query string.
URLSearch({
pagination: { pageIndex: 0, pageSize: 20 },
columnFilters: [{ id: 'status', value: 'active' }],
sorting: [{ id: 'year', desc: true }],
globalFilter: 'toyota',
});
// => "?page=1&limit=20&status=active&sort=-year&q=toyota"| Package | Version |
|---|---|
react / react-dom |
^19.x |
@mui/material |
^9.x |
@mui/icons-material |
^9.x |
@emotion/react / styled |
^11.x |
MIT