Data Grid - Column customization recipes
Advanced column customization recipes.
Persisting column width and order
When the columns prop reference is updated, the column width and order is reset to the colDef.width and the order of the colDef object and any updates will be lost.
This is because the Data Grid considers update of the columns prop as a new set of columns, and the previous state is discarded.
To persist the column width and order when the columns prop is updated, consider persisting the state of the columns in the userland.
With pivoting
Pivoting creates columns dynamically, based on the pivoting model and the row data.
To keep the column width of the pivoted grid after columns reference is changed, use groupingColDef and pivotingColDef props to apply the width updates to the autogenerated columns.
The demo below uses modified version of the useColumnsState() from the previous example in combination with a callbacks for grouping and pivoting columns.
Custom long text popup content
The default rendered cell of the longText column type uses GridLongTextCell component to show the popup with the full text content when the expand button is clicked. To customize the popup content, use the renderContent prop of the GridLongTextCell component.
The example below demonstrates two customizations:
- Character count: Shows character count below the text
- Markdown preview: Renders markdown content as formatted HTML
import * as React from 'react';
import Box from '@mui/material/Box';
import type { GridColDef } from '@mui/x-data-grid';
import { DataGrid, GridLongTextCell } from '@mui/x-data-grid';
import { styled } from '@mui/material/styles';
import Markdown from 'markdown-to-jsx';
const rows = [
{
id: 1,
title: 'Introduction to React',
notes:
'React is a JavaScript library for building user interfaces. It uses a component-based architecture and virtual DOM for efficient updates.',
markdown:
'# Getting Started\n\nTo create a new React app:\n\n```bash\nnpx create-react-app my-app\n```\n\n**Key concepts:**\n- Components\n- Props\n- State',
},
{
id: 2,
title: 'TypeScript Basics',
notes:
'TypeScript adds static type definitions to JavaScript. It helps catch errors early and improves code maintainability in large codebases.',
markdown:
'## Type Annotations\n\nBasic types:\n\n```typescript\nlet name: string = "John";\nlet age: number = 30;\nlet active: boolean = true;\n```\n\n*TypeScript compiles to plain JavaScript.*',
},
{
id: 3,
title: 'MUI Data Grid',
notes:
'The MUI X Data Grid is a powerful component for displaying and editing tabular data. It supports sorting, filtering, pagination, and many other features out of the box.',
markdown:
'### Features\n\n1. Sorting & Filtering\n2. Pagination\n3. Column resizing\n4. Row selection\n\n> The Data Grid is highly customizable!',
},
];
const CharacterCountWrapper = styled('div')({
display: 'flex',
flexDirection: 'column',
height: '100%',
});
const CharacterCountText = styled('span')(({ theme }) => ({
marginTop: 'auto',
paddingTop: theme.spacing(1),
fontSize: '0.75rem',
color: theme.palette.text.secondary,
}));
const MarkdownPreview = styled('div')(({ theme }) => ({
fontFamily: theme.typography.fontFamily,
'& h1, & h2, & h3': {
marginTop: 0,
marginBottom: theme.spacing(1),
fontSize: '1rem',
fontWeight: 600,
},
'& p': {
marginTop: 0,
marginBottom: theme.spacing(1),
},
'& ul, & ol': {
marginTop: 0,
marginBottom: theme.spacing(1),
paddingLeft: theme.spacing(2),
},
'& code': {
backgroundColor: theme.palette.action.hover,
padding: theme.spacing(0.25, 0.5),
borderRadius: theme.shape.borderRadius,
fontSize: '0.85em',
},
'& pre': {
backgroundColor: theme.palette.action.hover,
padding: theme.spacing(1),
borderRadius: theme.shape.borderRadius,
overflow: 'auto',
'& code': {
backgroundColor: 'transparent',
padding: 0,
},
},
'& blockquote': {
margin: 0,
marginBottom: theme.spacing(1),
paddingLeft: theme.spacing(1.5),
borderLeft: `3px solid ${theme.palette.divider}`,
color: theme.palette.text.secondary,
},
'& strong': {
fontWeight: 600,
},
'& em': {
fontStyle: 'italic',
},
}));
const columns: GridColDef[] = [
{ field: 'title', headerName: 'Title', width: 180, rowHeader: true },
{
field: 'notes',
headerName: 'Notes (with character count)',
type: 'longText',
width: 250,
editable: true,
renderCell: (params) => (
<GridLongTextCell
{...params}
renderContent={(value) => (
<CharacterCountWrapper>
<div>{value}</div>
<CharacterCountText>{value?.length ?? 0} characters</CharacterCountText>
</CharacterCountWrapper>
)}
/>
),
},
{
field: 'markdown',
headerName: 'Markdown (with preview)',
type: 'longText',
width: 250,
editable: true,
renderCell: (params) => (
<GridLongTextCell
{...params}
renderContent={(value) => (
<MarkdownPreview>
<Markdown>{value ?? ''}</Markdown>
</MarkdownPreview>
)}
slotProps={{
popperContent: {
style: {
minWidth: 375,
maxHeight: 'initial',
},
},
}}
/>
),
},
];
export default function LongTextCustomPopupContent() {
return (
<Box sx={{ height: 400, width: '100%' }}>
<DataGrid rows={rows} columns={columns} />
</Box>
);
}
Custom multi-select chips
To customize the chip appearance, customize the cell with the GridMultiSelectCell and GridEditMultiSelectCell components. Pass a function to slotProps.chip to derive chip props from each value.
The demo below shows two customizations:
- Colored chips: maps each tag to a different chip color
- Avatar chips: adds an avatar to each assignee chip
import * as React from 'react';
import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import Checkbox from '@mui/material/Checkbox';
import type { ChipProps } from '@mui/material/Chip';
import Chip from '@mui/material/Chip';
import type { GridColDef } from '@mui/x-data-grid-pro';
import {
DataGridPro,
GridMultiSelectCell,
GridEditMultiSelectCell,
} from '@mui/x-data-grid-pro';
import type { AutocompleteProps } from '@mui/x-data-grid/internals';
type ChipColor = NonNullable<ChipProps['color']>;
const tagColorMap: Record<string, ChipColor> = {
Bug: 'error',
Feature: 'primary',
Enhancement: 'info',
Documentation: 'warning',
Performance: 'success',
};
const tagOptions = Object.keys(tagColorMap);
interface UserOption {
value: string;
label: string;
avatar: string;
}
const departmentColors: Record<string, { bg: string; color: string }> = {
Support: { bg: '#f0f0f0', color: '#555' },
Sales: { bg: '#e8dff5', color: '#6b21a8' },
Marketing: { bg: '#dbeafe', color: '#1e40af' },
Product: { bg: '#fce7f3', color: '#9d174d' },
DevEx: { bg: '#ffedd5', color: '#9a3412' },
Design: { bg: '#fef9c3', color: '#854d0e' },
'Engineering Management': { bg: '#e8e0d8', color: '#78716c' },
Legal: { bg: '#dcfce7', color: '#166534' },
Operations: { bg: '#e5e5e5', color: '#525252' },
People: { bg: '#fef3c7', color: '#92400e' },
Finance: { bg: '#dbeafe', color: '#1e3a8a' },
Engineering: { bg: '#ffe4e6', color: '#be123c' },
};
const departmentOptions = Object.keys(departmentColors);
const userOptions: UserOption[] = [
{ value: 'alice', label: 'Alice', avatar: 'A' },
{ value: 'bob', label: 'Bob', avatar: 'B' },
{ value: 'charlie', label: 'Charlie', avatar: 'C' },
{ value: 'diana', label: 'Diana', avatar: 'D' },
{ value: 'eve', label: 'Eve', avatar: 'E' },
];
const rows = [
{
id: 1,
title: 'Fix login crash',
tags: ['Bug'],
assignees: ['alice', 'bob'],
departments: ['Engineering', 'Support'],
},
{
id: 2,
title: 'Add dark mode',
tags: ['Feature', 'Enhancement'],
assignees: ['charlie'],
departments: ['Design', 'Product'],
},
{
id: 3,
title: 'Update API docs',
tags: ['Documentation'],
assignees: ['diana', 'eve'],
departments: ['DevEx'],
},
{
id: 4,
title: 'Optimize queries',
tags: ['Performance', 'Bug'],
assignees: ['alice', 'eve', 'bob'],
departments: ['Engineering', 'Operations'],
},
];
const getTagChipProps = (value: string) => ({
color: tagColorMap[value] ?? ('default' as ChipColor),
variant: 'filled' as const,
});
const getAssigneeChipProps = (option: UserOption) => ({
avatar: (
<Avatar sx={{ width: 24, height: 24, fontSize: 12 }}>{option.avatar}</Avatar>
),
});
const getDepartmentChipProps = (value: string) => {
const colors = departmentColors[value] ?? { bg: '#f0f0f0', color: '#555' };
return {
variant: 'filled' as const,
sx: {
backgroundColor: colors.bg,
color: colors.color,
fontWeight: 500,
'& .MuiChip-deleteIcon': {
color: colors.color,
opacity: 0.6,
'&:hover': { opacity: 1, color: colors.color },
},
},
};
};
const renderDepartmentOption: NonNullable<
AutocompleteProps<string>['renderOption']
> = (props, option, { selected }) => {
const colors = departmentColors[option] ?? { bg: '#f0f0f0', color: '#555' };
return (
<li {...props}>
<Checkbox checked={selected} sx={{ mr: 1, p: 0 }} />
<Chip
label={option}
size="small"
sx={{
backgroundColor: colors.bg,
color: colors.color,
fontWeight: 500,
}}
/>
</li>
);
};
const columns: GridColDef[] = [
{ field: 'title', headerName: 'Title', width: 160, rowHeader: true },
{
field: 'tags',
headerName: 'Tags (colored)',
type: 'multiSelect',
width: 220,
editable: true,
valueOptions: tagOptions,
renderCell: (params) => (
<GridMultiSelectCell {...params} slotProps={{ chip: getTagChipProps }} />
),
renderEditCell: (params) => (
<GridEditMultiSelectCell {...params} slotProps={{ chip: getTagChipProps }} />
),
},
{
field: 'assignees',
headerName: 'Assignees (avatar)',
type: 'multiSelect',
width: 260,
editable: true,
valueOptions: userOptions,
renderCell: (params) => (
<GridMultiSelectCell {...params} slotProps={{ chip: getAssigneeChipProps }} />
),
renderEditCell: (params) => (
<GridEditMultiSelectCell
{...params}
slotProps={{ chip: getAssigneeChipProps }}
/>
),
},
{
field: 'departments',
headerName: 'Departments (custom options)',
type: 'multiSelect',
width: 280,
editable: true,
valueOptions: departmentOptions,
renderCell: (params) => (
<GridMultiSelectCell
{...params}
slotProps={{ chip: getDepartmentChipProps }}
/>
),
renderEditCell: (params) => (
<GridEditMultiSelectCell
{...params}
slotProps={{
chip: getDepartmentChipProps,
autocomplete: { renderOption: renderDepartmentOption },
}}
/>
),
},
];
export default function MultiSelectCustomChips() {
return (
<Box sx={{ height: 400, width: '100%' }}>
<DataGridPro rows={rows} columns={columns} />
</Box>
);
}