Data Grid - Custom slots and subcomponents
Learn how to override parts of the grid.
Interacting with the Data Grid
The grid exposes two hooks to help you access the Data Grid data while overriding component slots.
They can be used as below:
useGridApiContext(): returns theapiRefobject (more details on the API object page).useGridSelector(): returns the result of a selector on the current state (more details on the State page).
function CustomPagination() {
const apiRef = useGridApiContext();
const paginationModel = useGridSelector(apiRef, gridPaginationModelSelector);
const pageCount = useGridSelector(apiRef, gridPageCountSelector);
return (
<Pagination
count={pageCount}
page={paginationModel.page + 1}
onChange={(event, value) => apiRef.current.setPage(value - 1)}
/>
);
}
Component slots
Columns panel
In the following demo, the columns panel is replaced with a custom component that represents the column groups as a nested list.
import * as React from 'react';
import Box from '@mui/material/Box';
import type {
GridColDef,
GridColumnGroup,
GridColumnGroupingModel,
GridColumnVisibilityModel,
GridApi,
GridColumnLookup,
GridColumnsPanelProps,
} from '@mui/x-data-grid';
import {
DataGrid,
gridColumnVisibilityModelSelector,
useGridApiContext,
useGridRootProps,
isLeaf,
gridColumnLookupSelector,
useGridSelector,
GridColumnsPanel,
GridPreferencePanelsValue,
} from '@mui/x-data-grid';
import Checkbox from '@mui/material/Checkbox';
import FormControlLabel from '@mui/material/FormControlLabel';
import Stack from '@mui/material/Stack';
const getColumnGroupLeaves = (
group: GridColumnGroup,
callback: (field: string) => void,
) => {
group.children.forEach((child) => {
if (isLeaf(child)) {
callback(child.field);
} else {
getColumnGroupLeaves(child, callback);
}
});
};
function ColumnGroup({
group,
columnLookup,
apiRef,
columnVisibilityModel,
}: {
group: GridColumnGroup;
columnLookup: GridColumnLookup;
apiRef: React.RefObject<GridApi>;
columnVisibilityModel: GridColumnVisibilityModel;
}) {
const leaves = React.useMemo(() => {
const fields: string[] = [];
getColumnGroupLeaves(group, (field) => fields.push(field));
return fields;
}, [group]);
const { isGroupChecked, isGroupIndeterminate } = React.useMemo(() => {
return {
isGroupChecked: leaves.every(
(field) => columnVisibilityModel[field] !== false,
),
isGroupIndeterminate:
leaves.some((field) => columnVisibilityModel[field] === false) &&
!leaves.every((field) => columnVisibilityModel[field] === false),
};
}, [columnVisibilityModel, leaves]);
const toggleColumnGroup = (checked: boolean) => {
const newColumnVisibilityModel = {
...columnVisibilityModel,
};
getColumnGroupLeaves(group, (field) => {
newColumnVisibilityModel[field] = checked;
});
apiRef.current.setColumnVisibilityModel(newColumnVisibilityModel);
};
const toggleColumn = (field: string, checked: boolean) => {
apiRef.current.setColumnVisibility(field, checked);
};
return (
<div>
<FormControlLabel
control={
<Checkbox
checked={isGroupChecked}
indeterminate={isGroupIndeterminate}
size="small"
sx={{ p: 1 }}
/>
}
label={group.headerName ?? group.groupId}
onChange={(_, newValue) => toggleColumnGroup(newValue)}
/>
<Box sx={{ pl: 3.5 }}>
{group.children.map((child) => {
return isLeaf(child) ? (
<Stack direction="row" key={child.field}>
<FormControlLabel
control={
<Checkbox
checked={columnVisibilityModel[child.field] !== false}
size="small"
sx={{ p: 1 }}
/>
}
label={columnLookup[child.field].headerName ?? child.field}
onChange={(_, newValue) => toggleColumn(child.field, newValue)}
/>
</Stack>
) : (
<ColumnGroup
group={child}
columnLookup={columnLookup}
key={child.groupId}
apiRef={apiRef}
columnVisibilityModel={columnVisibilityModel}
/>
);
})}
</Box>
</div>
);
}
function ColumnsPanel(props: GridColumnsPanelProps) {
const apiRef = useGridApiContext();
const rootProps = useGridRootProps();
const columnLookup = useGridSelector(apiRef, gridColumnLookupSelector);
const columnVisibilityModel = useGridSelector(
apiRef,
gridColumnVisibilityModelSelector,
);
const columnGroupingModel = rootProps.columnGroupingModel;
if (!columnGroupingModel) {
return <GridColumnsPanel {...props} />;
}
return (
<Box sx={{ px: 2, py: 0.5 }}>
{columnGroupingModel.map((group) => (
<ColumnGroup
group={group}
columnLookup={columnLookup}
columnVisibilityModel={columnVisibilityModel}
key={group.groupId}
apiRef={apiRef}
/>
))}
</Box>
);
}
const columnGroupingModel: GridColumnGroupingModel = [
{
groupId: 'Internal',
description: '',
children: [{ field: 'id' }],
},
{
groupId: 'character',
description: 'Information about the character',
headerName: 'Basic info',
children: [
{
groupId: 'naming',
headerName: 'Names',
children: [{ field: 'lastName' }, { field: 'firstName' }],
},
{ field: 'age' },
],
},
];
const columns: GridColDef[] = [
{ field: 'id', headerName: 'ID', width: 150 },
{ field: 'firstName', headerName: 'First name', width: 150, rowHeader: true },
{ field: 'lastName', headerName: 'Last name', width: 150, rowHeader: true },
{ field: 'age', headerName: 'Age', type: 'number', width: 110 },
];
const rows = [
{ id: 1, lastName: 'Snow', firstName: 'Jon', age: 35 },
{ id: 2, lastName: 'Lannister', firstName: 'Cersei', age: 42 },
{ id: 3, lastName: 'Lannister', firstName: 'Jaime', age: 45 },
{ id: 4, lastName: 'Stark', firstName: 'Arya', age: 16 },
{ id: 5, lastName: 'Targaryen', firstName: 'Daenerys', age: null },
{ id: 6, lastName: 'Melisandre', firstName: null, age: 150 },
{ id: 7, lastName: 'Clifford', firstName: 'Ferrara', age: 44 },
{ id: 8, lastName: 'Frances', firstName: 'Rossini', age: 36 },
{ id: 9, lastName: 'Roxie', firstName: 'Harvey', age: 65 },
];
export default function CustomColumnsPanel() {
return (
<Box sx={{ height: 400, width: '100%' }}>
<DataGrid
rows={rows}
columns={columns}
checkboxSelection
disableRowSelectionOnClick
columnGroupingModel={columnGroupingModel}
slots={{ columnsPanel: ColumnsPanel }}
initialState={{
preferencePanel: {
open: true,
openedPanelValue: GridPreferencePanelsValue.columns,
},
}}
/>
</Box>
);
}
Column menu
As mentioned above, the column menu is a component slot that can be recomposed easily and customized on each column as in the demo below.
Toolbar
Pass the showToolbar prop to the <DataGrid /> component to enable the default toolbar.
import { DataGrid } from '@mui/x-data-grid';
import { useDemoData } from '@mui/x-data-grid-generator';
export default function ToolbarGrid() {
const { data, loading } = useDemoData({
dataSet: 'Commodity',
rowLength: 100,
maxColumns: 6,
});
return (
<div style={{ height: 400, width: '100%' }}>
<DataGrid {...data} loading={loading} showToolbar />
</div>
);
}
You can also compose your own toolbar using the Toolbar component.
Legacy toolbar
Each button in the toolbar is wrapped with a tooltip component.
In order to override some of the props corresponding to the toolbar buttons, you can use the slotProps prop.
The following demo shows how to override the tooltip title of the density selector and the variant of the export button.
function CustomToolbar() {
return (
<GridToolbarContainer>
<GridToolbarColumnsButton />
<GridToolbarFilterButton />
<GridToolbarDensitySelector
slotProps={{ tooltip: { title: 'Change density' } }}
/>
<Box sx={{ flexGrow: 1 }} />
<GridToolbarExport
slotProps={{
tooltip: { title: 'Export data' },
button: { variant: 'outlined' },
}}
/>
</GridToolbarContainer>
);
}
Footer
The grid exposes props to hide specific elements of the UI:
hideFooter: Iftrue, the footer component is hidden.hideFooterRowCount: Iftrue, the row count in the footer is hidden.hideFooterSelectedRowCount: Iftrue, the selected row count in the footer is hidden.hideFooterPagination: Iftrue, the pagination component in the footer is hidden.
Pagination
The default pagination component is exported as GridPagination.
This component is an extension of the TablePagination component, and it renders the page size control, the number of rows in the page and also the buttons to go to the previous and next page.
You can replace the pagination component completely or reuse the default one.
The next demo reuses GridPagination but replaces the previous and next page buttons with Pagination, which renders a dedicated button for each page.
Row
The slotProps.row prop can be used to pass additional props to the row component.
One common use case might be to listen for events not exposed by default.
The demo below shows a context menu when a row is right-clicked.
Cell
The following demo uses the slotProps.cell prop to listen for specific events emitted by the cells.
Try it by hovering a cell with the mouse and it should display the number of characters each cell has.
Icons
As any component slot, every icon can be customized. However, it is not yet possible to use the slotProps with icons.
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import SortIcon from '@mui/icons-material/Sort';
import { DataGrid } from '@mui/x-data-grid';
export function SortedDescendingIcon() {
return <ExpandMoreIcon className="icon" />;
}
export function SortedAscendingIcon() {
return <ExpandLessIcon className="icon" />;
}
export function UnsortedIcon() {
return <SortIcon className="icon" />;
}
const rows = [
{
id: 1,
name: 'MUI',
stars: 28000,
},
{
id: 2,
name: 'DataGrid',
stars: 15000,
},
];
const columns = [
{ field: 'name', width: 150, rowHeader: true },
{ field: 'stars', width: 150 },
];
export default function CustomSortIcons() {
return (
<div style={{ height: 250, width: '100%' }}>
<DataGrid
columns={columns}
rows={rows}
initialState={{
sorting: { sortModel: [{ field: 'name', sort: 'asc' }] },
}}
slots={{
columnSortedDescendingIcon: SortedDescendingIcon,
columnSortedAscendingIcon: SortedAscendingIcon,
columnUnsortedIcon: UnsortedIcon,
}}
/>
</div>
);
}
Overlays
See the Overlays documentation on how to customize the loadingOverlay, noRowsOverlay, and noResultsOverlay.
Custom slot props with TypeScript
If the custom component requires additional props to work properly, TypeScript may throw type errors. To solve these type errors, use module augmentation to enhance the props interface.
The naming of overridable interfaces uses a pattern like this:
`${slotNameInPascalCase}PropsOverrides`;
For example, for columnMenu slot, the interface name would be ColumnMenuPropsOverrides.
This file lists all the interfaces for each slot that could be used for augmentation.
// augment the props for the toolbar slot
declare module '@mui/x-data-grid' {
interface ToolbarPropsOverrides {
someCustomString: string;
someCustomNumber: number;
}
}
<DataGrid
slots={{
// custom component passed to the toolbar slot
toolbar: CustomGridToolbar,
}}
slotProps={{
toolbar: {
// props used by CustomGridToolbar
someCustomString: 'Hello',
someCustomNumber: 42,
},
}}
showToolbar
/>;This demo below shows how to use the slotProps prop and module augmentation to pass a new prop status to the footer slot.