Theme

Examples › Column properties

The accessor

The only property you have to specify for a column is its accessor (the name of the record property you want to display in each column cell).
The accessor supports dot-notation for nested objects property drilling (i.e. 'department.company.name').
The component will try to derive a column header title by “humanizing” the provided accessor (i.e. 'firstName' → 'First name' or 'department.company.name' → 'Department company name').
If you’re not happy with the automatically derived title, you can override it by setting your own column title.

Basic column properties

In addition, each column can be customized by specifying the following properties:
  • width → desired column width as a number or string.
  • ellipsisboolean; if true, cell content in this column will not wrap to multiple lines and will be truncated with ellipsis if/as needed; you can either set this property to true or set noWrap to true, but not both.
  • noWrapboolean; if true, cell content in this column will not wrap on multiple lines (i.e. white-space: nowrap); you can either set this property to true or set ellipsis to true, but not both.
  • textAlignment'left' | 'center' | 'right'; defaults to 'left' if not specified.
  • hidden → if true, the column will not be visible.
  • visibleMediaQuery → a media query string or a function accepting the current MantineTheme as its argument and returning a media-query string; if set, the column will only be visible according to the specified media query.
  • render → a method that accepts the current record as its argument and must return a ReactNode.
  • filter * → an optional node which provides the user with filtering options. If present, a filter button will be added to the column header. Upon clicking the button, a pop-over showing the provided node will be opened. Alternatively, you can provide a function returning a ReactNode. The function will be called with an object containing a close method, which you can call to close the pop-over.
  • filtering * → if true, the column will be styled as an active filtering column; defaults to false if not specified.
You can create a “virtual column” by providing an accessor that doesn’t to refer an existing property (or nested property) name. In this case, you must provide a custom render method. Also, keep in mind that each accessor name must be unique amongst the collection of columns.
Consider the example below:
import dayjs from 'dayjs';
import { DataTable } from 'mantine-datatable';
import { employees } from '~/data';
const records = employees.slice(0, 10);
export default function ColumnPropertiesExample() {
return (
<DataTable
withBorder
withColumnBorders
striped
records={records}
columns={[
{
accessor: 'index',
title: '#',
textAlignment: 'right',
width: 40,
render: (record) => records.indexOf(record) + 1,
},
{
accessor: 'name',
title: 'Full name',
render: ({ firstName, lastName }) => `${firstName} ${lastName}`,
width: 160,
},
{ accessor: 'email' },
{ accessor: 'department.name', width: 150 },
{
// using dot-notation to access nested object property
accessor: 'department.company.name',
title: 'Company',
width: 150,
// truncate with ellipsis if text overflows the available width
ellipsis: true,
},
{
accessor: 'birthDate',
title: 'Birthday',
width: 100,
render: ({ birthDate }) => dayjs(birthDate).format('MMM D'),
// column is only visible when screen width is over `theme.breakpoints.xs`
visibleMediaQuery: (theme) => `(min-width: ${theme.breakpoints.xs})`,
},
{
// "virtual column"
accessor: 'age',
width: 60,
textAlignment: 'right',
// column is only visible when screen width is over `theme.breakpoints.xs`
visibleMediaQuery: (theme) => `(min-width: ${theme.breakpoints.xs})`,
render: ({ birthDate }) => dayjs().diff(birthDate, 'years'),
},
]}
/>
);
}
import dayjs, { Dayjs } from 'dayjs';
import get from 'lodash/get';
import sortBy from 'lodash/sortBy';
import { DataTableSortStatus } from 'mantine-datatable';
import companyData from '~/data/companies.json';
import departmentData from '~/data/departments.json';
import employeeData from '~/data/employees.json';
import delay, { DelayOptions } from '~/lib/delay';
export type Company = {
id: string;
name: string;
streetAddress: string;
city: string;
state: string;
missionStatement: string;
};
export type Department = {
id: string;
name: string;
company: Company;
};
export type Employee = {
id: string;
firstName: string;
lastName: string;
email: string;
birthDate: string;
department: Department;
};
export const companies: Company[] = [...companyData];
export const departments: Department[] = departmentData.map(({ companyId, ...rest }) => ({
...rest,
company: companies.find(({ id }) => id === companyId)!,
}));
export const employees: Employee[] = employeeData.map(({ departmentId, ...rest }) => ({
...rest,
department: departments.find(({ id }) => id === departmentId)!,
}));
export async function getCompaniesAsync({
count,
delay: delayOptions = { min: 1000, max: 2000 },
}: {
count: number;
delay?: DelayOptions;
}) {
await delay(delayOptions);
return companies.slice(0, count);
}
export async function getEmployeesAsync({
page,
recordsPerPage,
sortStatus: { columnAccessor: sortAccessor, direction: sortDirection },
delay: delayOptions = { min: 1000, max: 2000 },
}: {
page: number;
recordsPerPage: number;
sortStatus: DataTableSortStatus;
delay?: DelayOptions;
}) {
await delay(delayOptions);
let now: Dayjs;
if (sortAccessor === 'age') now = dayjs();
let result = sortBy(employees, (employee) =>
sortAccessor === 'name'
? `${employee.firstName} ${employee.lastName}`
: sortAccessor === 'age'
? now.diff(employee.birthDate)
: get(employee, sortAccessor)
);
if (sortDirection === 'desc') result.reverse();
const total = result.length;
const skip = (page - 1) * recordsPerPage;
result = result.slice(skip, skip + recordsPerPage);
return { total, employees: result as Employee[] };
}
export async function countCompanyDepartmentsAsync({
companyId,
delay: delayOptions,
}: {
companyId: string;
delay: DelayOptions;
}) {
await delay(delayOptions);
return departments.filter((department) => department.company.id === companyId).length;
}
export async function countCompanyEmployeesAsync({
companyId,
delay: delayOptions,
}: {
companyId: string;
delay: DelayOptions;
}) {
await delay(delayOptions);
return employees.filter((employee) => employee.department.company.id === companyId).length;
}
The code above will produce the following result:
1Dustin SawaynDustin.Sawayn@yahoo.comIndustrialRunolfsdottir - CummerataMay 2043
2Earnest HellerEarnest6@yahoo.comHealthJohnston LLCNov 2632
3Sheila KesslerSheila.Kessler@hotmail.comAutomotiveSipes IncApr 1241
4Brayan MacejkovicBrayan3@hotmail.comBabyJohnston LLCJun 3077
5Assunta AnkundingAssunta79@gmail.comBooksCrist and SonsMay 2645
6Javonte SchummJavonte14@gmail.comClothingRunolfsdottir - CummerataMar 467
7Jennifer VonJennifer_Von87@hotmail.comShoesWelch - TremblayFeb 2238
8Mozell BeckerMozell.Becker@yahoo.comGroceryJakubowski - RolfsonSep 2049
9Horace BayerHorace.Bayer24@yahoo.comHomeJakubowski - RolfsonJul 427
10Chauncey HoegerChauncey.Hoeger7@gmail.comClothingJohnston LLCApr 841
No records

Column footers

The DataTable component will display a footer row at the bottom of the table if you specify a footer property for at least one column:
<DataTable
withBorder
withColumnBorders
striped
records={records}
columns={[
{
accessor: 'name',
title: 'Full name',
render: ({ firstName, lastName }) => `${firstName} ${lastName}`,
width: 160,
footer: (
<Group spacing="xs">
<IconSum size="1.25em" />
<Text mb={-2}>{records.length} employees</Text>
</Group>
),
},
{ accessor: 'department.name', width: 150 },
{
accessor: 'department.company.name',
title: 'Company',
width: 150,
ellipsis: true,
footer: (
<Group spacing="xs">
<IconSum size="1.25em" />
<Text mb={-2}>{uniqBy(records, (record) => record.department.company.name).length} companies</Text>
</Group>
),
},
{
accessor: 'age',
width: 60,
textAlignment: 'right',
visibleMediaQuery: (theme) => `(min-width: ${theme.breakpoints.xs})`,
render: ({ birthDate }) => dayjs().diff(birthDate, 'years'),
footer: `Avg: ${Math.round(
records.map((record) => dayjs().diff(record.birthDate, 'years')).reduce((a, b) => a + b, 0) / records.length
)}`,
},
]}
/>
The code above will produce the following result:
Dustin SawaynIndustrialRunolfsdottir - Cummerata43
Earnest HellerHealthJohnston LLC32
Sheila KesslerAutomotiveSipes Inc41
Brayan MacejkovicBabyJohnston LLC77
Assunta AnkundingBooksCrist and Sons45
Javonte SchummClothingRunolfsdottir - Cummerata67
Jennifer VonShoesWelch - Tremblay38
Mozell BeckerGroceryJakubowski - Rolfson49
Horace BayerHomeJakubowski - Rolfson27
Chauncey HoegerClothingJohnston LLC41
10 employees
6 companies
Avg: 46
No records

Styling column titles, cells and footers

In addition, each column can be further customized by specifying the following styling properties:
  • titleClassName *
  • titleStyle
  • titleSx *
  • cellsClassName
  • cellsStyle
  • cellsSx
  • footerClassName *
  • footerStyle
  • footerSx *
import { createStyles } from '@mantine/core';
import dayjs from 'dayjs';
import { DataTable, uniqBy } from 'mantine-datatable';
import { employees } from '~/data';
const records = employees.slice(0, 10);
const useStyles = createStyles((theme) => ({
idColumnCells: { fontWeight: 700 },
birthdayColumnTitle: { '&&': { color: theme.colors.blue[6] } },
birthdayInApril: {
fontWeight: 700,
color: theme.colors.blue[6],
background: theme.fn.rgba(theme.colors.yellow[6], 0.25),
},
ageFooter: {
'&&': { color: theme.colors.red[6] },
background: theme.fn.rgba(theme.colors.yellow[6], 0.25),
},
}));
export default function ColumnPropertiesExampleStyling() {
const { classes, cx } = useStyles();
return (
<DataTable
withBorder
withColumnBorders
striped
records={records}
columns={[
{
accessor: 'index',
title: '#',
textAlignment: 'right',
width: 40,
// style cells with a class name
cellsClassName: classes.idColumnCells,
render: (record) => records.indexOf(record) + 1,
},
{
accessor: 'name',
title: 'Full name',
width: 160,
// style cells with a CSS properties object
cellsStyle: { fontStyle: 'italic' },
render: ({ firstName, lastName }) => `${firstName} ${lastName}`,
footer: `${records.length} employees`,
// style footer with a CSS properties object
footerStyle: { fontStyle: 'italic' },
},
{ accessor: 'email' },
{
accessor: 'department.name',
width: 150,
// style title with an Sx object
titleSx: (theme) => ({ '&&': { color: theme.colors.green[6] } }),
// style cells with an Sx object
cellsSx: (theme) => ({
color: theme.colors.green[8],
background: theme.fn.rgba(theme.colors.orange[6], 0.25),
}),
},
{
accessor: 'department.company.name',
title: 'Company',
width: 150,
ellipsis: true,
footer: `${uniqBy(records, (record) => record.department.company.name).length} companies`,
// style footer with an Sx object
footerSx: (theme) => ({ '&&': { color: theme.colors.blue[6] } }),
},
{
accessor: 'birthDate',
title: 'Birthday',
width: 100,
// style title with a custom class name
titleClassName: classes.birthdayColumnTitle,
// style cells with a class name depending on current record
cellsClassName: ({ birthDate }) => cx({ [classes.birthdayInApril]: dayjs(birthDate).format('MM') === '04' }),
render: ({ birthDate }) => dayjs(birthDate).format('MMM D'),
},
{
accessor: 'age',
width: 80,
textAlignment: 'right',
// style title with a CSS properties object
titleStyle: { fontStyle: 'italic' },
// style cells with a CSS properties object depending on current record
cellsStyle: ({ birthDate }) =>
dayjs().diff(birthDate, 'years') <= 40
? {
fontWeight: 'bold',
color: 'green',
background: '#FF332222',
}
: undefined,
render: ({ birthDate }) => dayjs().diff(birthDate, 'years'),
footer: `Avg: ${Math.round(
records.map((record) => dayjs().diff(record.birthDate, 'years')).reduce((a, b) => a + b, 0) / records.length
)}`,
footerClassName: classes.ageFooter,
},
]}
/>
);
}
The code above will produce the following result:
1Dustin SawaynDustin.Sawayn@yahoo.comIndustrialRunolfsdottir - CummerataMay 2043
2Earnest HellerEarnest6@yahoo.comHealthJohnston LLCNov 2632
3Sheila KesslerSheila.Kessler@hotmail.comAutomotiveSipes IncApr 1241
4Brayan MacejkovicBrayan3@hotmail.comBabyJohnston LLCJun 3077
5Assunta AnkundingAssunta79@gmail.comBooksCrist and SonsMay 2645
6Javonte SchummJavonte14@gmail.comClothingRunolfsdottir - CummerataMar 467
7Jennifer VonJennifer_Von87@hotmail.comShoesWelch - TremblayFeb 2238
8Mozell BeckerMozell.Becker@yahoo.comGroceryJakubowski - RolfsonSep 2049
9Horace BayerHorace.Bayer24@yahoo.comHomeJakubowski - RolfsonJul 427
10Chauncey HoegerChauncey.Hoeger7@gmail.comClothingJohnston LLCApr 841
10 employees6 companiesAvg: 46
No records

Mantine DataTable is trusted by

MIT LicenseSponsor the author
Built by Ionut-Cristian Florescu and these awesome people.
Please sponsor the project if you find it useful.
GitHub StarsNPM Downloads