Skip to content

mui-datatables alternative — maintained, MUI v6+, React 19

See it before you install: the live demo running on real Material UI — nothing to set up.

mui-datatables served a generation of Material UI apps well — but it has had no releases or commits since January 2023 (last version 4.3.0). Its peer range stops at @mui/material ^5, so MUI v6 and v7 apps can’t install it cleanly, React 19 is unsupported, and ~624 open issues sit unaddressed. If your app is stuck on MUI v5 because of your table, this page is the way out.

@adapttable/mui renders real Material UI components — supported across MUI v5 through v9 — so migrating unfreezes your MUI upgrade path while keeping the Material look. This page maps the API across and shows a before/after.

All verifiable as of mid-2026:

  • Unmaintained: no releases or commits since January 2023; hundreds of open issues and PRs without a response.
  • Version lock: peer dependencies require @mui/material ^5.11 — npm refuses clean installs on MUI v6/v7, and React 19 is outside the peer range. Your table shouldn’t decide your framework versions.
  • Weight: ~102 kB gzipped with 18 runtime dependencies (it bundles drag-and-drop, print, and CSV machinery whether you use them or not). AdaptTable core + the MUI adapter is ~45 kB min+gzip, tree-shaken.
  • No bundled types: TypeScript definitions live in community @types/mui-datatables and can drift. AdaptTable is TypeScript-first.

Beyond the unfreeze: URL-synced shareable state (filters, search, sort, page in the query string — reload-safe), one data contract for client and server data instead of the onTableChange action switch, chips for active filters, an automatic mobile card layout (no responsive mode quirks), saved views, and opt-in virtualization. See the feature overview.

Terminal window
pnpm add @adapttable/core @adapttable/mui @mui/material

Works with your existing MUI theme — and lets you upgrade @mui/material whenever you want.

<MUIDataTable><DataTable>:

mui-datatables@adapttable/muiNotes
data (objects)dataArray-of-objects only; no array-of-arrays mode.
data (arrays)convert to objectsOne map() at the boundary — see Gotchas.
columns (name, label, options)columns (key, header, …)Field-by-field below.
options.serverSide + count + onTableChangedata + total + onQueryChangeOne consolidated query object replaces the action-string switch.
options.page / rowsPerPage / rowsPerPageOptionsautomaticBuilt-in pager; page size control included.
options.sortOrder / onColumnSortChangeper-column sortable (+ multiSort)AdaptTable owns and applies sort state.
options.filterType / onFilterChangecolumn filter shorthand + table filtersKit-native widgets + removable chips, derived for you.
options.search / searchText / onSearchChangebuilt-in search (searchPlaceholder, hideSearch)Debounced and URL-synced.
options.selectableRows + onRowSelectionChangebulkActions, or selectedIds / onSelectionChangeProviding bulkActions enables selection + the bulk bar.
options.expandableRows + renderExpandableRowrenderRowDetail(row) => ReactNode — no separate flag, no colSpan markup.
options.viewColumns / draggableColumnsenableColumnMenuShow/hide, reorder, and pin in one menu.
options.resizableColumnsresizableColumnsDrag + keyboard resize.
options.responsive ('vertical', …)automatic mobile cardsCards render below the breakpoint — no mode to configure.
options.download / printexportCsv prop / rowsToCsv + downloadCsvBuilt-in Export CSV button, or headless helpers on a custom toolbar button; print via CSS.
options.textLabelslabelsSame idea; English defaults fill missing keys.
options.storageKeysavedViews / URL stateViews are named, shareable snapshots instead of one implicit save.

Column options → ColumnDef:

mui-datatables@adapttable/muiNotes
namekeyDot paths reach nested values without enableNestedDataAccess.
labelheaderAuto-derived from key when omitted.
options.customBodyRender(value, tableMeta)Cell (component) or accessor: (row)You get the row object, not index bookkeeping.
options.customBodyRenderLite(dataIndex)CellRow memoization is built in — no “lite” variant needed.
options.display / viewColumnsenableColumnMenu (+ columnLayout)User-facing visibility lives in the Columns menu.
options.filter / filterType / filterOptionsfilter shorthand"text", "select", "multiSelect", "numberRange", "dateRange".
options.sort / sortComparesortable / sortValuesortValue extracts the comparable primitive.
options.setCellProps / setCellHeaderPropsalign, width, className hooksCommon cases are first-class props.

Server-side data: retire the action switch

Section titled “Server-side data: retire the action switch”

mui-datatables server mode means serverSide: true, a count, and an onTableChange(action, tableState) callback where you switch over action strings — 'changePage', 'sort', 'search', 'filterChange', 'changeRowsPerPage' — re-fetching in each case (the library throws if onTableChange is missing). AdaptTable replaces the whole dispatch with one callback receiving one consolidated query:

<DataTable
data={rows}
total={total}
loading={loading}
onQueryChange={async (query, { signal }) => {
// query: { page, limit, search, sortBy, sortDir, filters } — one shape,
// fired once per real change (mount included), previous fetch aborted.
const res = await fetch(`/api/people?${toParams(query)}`, { signal });
const body = await res.json();
setRows(body.items);
setTotal(body.total);
}}
columns={columns}
rowKey={(r) => r.id}
/>

Before — mui-datatables:

import MUIDataTable from "mui-datatables";
function PeopleTable({ people }) {
return (
<MUIDataTable
title="People"
data={people}
columns={[
{ name: "name", label: "Name" },
{ name: "role", label: "Role" },
{
name: "status",
label: "Status",
options: {
filter: true,
customBodyRender: (value) => <StatusChip value={value} />,
},
},
]}
options={{
filterType: "dropdown",
selectableRows: "multiple",
responsive: "vertical",
}}
/>
);
}

After@adapttable/mui (typed, URL-synced, MUI v5–v9):

import { type CellProps, DataTable } from "@adapttable/mui";
function StatusCell({ row }: CellProps<Person>) {
return <StatusChip value={row.status} />;
}
function PeopleTable({ people }: { people: Person[] }) {
return (
<DataTable
data={people}
rowKey={(r) => r.id}
bulkActions={bulkActions}
columns={[
{ key: "name", sortable: true },
{ key: "role" },
{
key: "status",
Cell: StatusCell,
filter: { type: "select", options: "auto" },
},
]}
/>
);
}
  • Array-of-arrays data isn’t supported. If your data is [["Ada", "Engineer"], …], map it to objects once at the boundary: rows.map(([name, role], i) => ({ id: String(i), name, role })) — and use a real id when you have one.
  • rowKey is required. mui-datatables keys rows by index internally; AdaptTable wants a stable id.
  • customBodyRender receives a value; Cell receives the row. Rewrites usually get simpler — no tableMeta.rowData[…] index lookups.
  • Filters become declarative. A filterType: "dropdown" column becomes filter: { type: "select", options: "auto" }; ranges become "numberRange" / "dateRange" with operator widgets and chips included.
  • CSV export is built-in and headless. Pass exportCsv for a toolbar button, or wire rowsToCsv / downloadCsv yourself via the toolbar slot; print is your app’s concern.
  • textLabelslabels, and locale presets (incl. RTL languages) come from @adapttable/i18n.