Examples › Column properties and styling

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: number | string
    Desired column width.
  • ellipsis: boolean
    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.
  • noWrap: boolean
    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.
  • textAlign: 'left' | 'center' | 'right'
    Defaults to 'left' if not specified.
  • hidden: boolean
    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 returns a ReactNode (keep in mind that strings and numbers are valid react nodes).
  • filter
    An optional property which provides the user with filtering options. It can be either a ReactNode or a function returning a ReactNode.
    If a ReactNode is provided, a filter button will be added to the column header. Upon clicking the button, a popover 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 popover.
  • filtering: boolean
    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 this example:

#
Full name
Email
Department name
Company
Birthday
Age
1Jerald HowellJerald.Howell32@yahoo.comIndustrialRunte IncMay 2174
2Kathleen RueckerKathleen_Ruecker@hotmail.comComputersShanahan, Robel and BeierDec 1980
3Erica VolkmanErica.Volkman37@gmail.comToysGoyette IncJan 2969
4Clifford OberbrunnerClifford.Oberbrunner@hotmail.comAutomotiveRau - O'HaraMar 645
5Alison KlingAlison16@gmail.comJeweleryGoyette IncJan 2727
6Sue ZiemeSue.Zieme29@hotmail.comBooksCummerata - KuhlmanSep 1264
7Felicia GleasonFelicia30@yahoo.comShoesDoyle, Hodkiewicz and O'ConnellMar 947
8Alfredo ZemlakAlfredo22@yahoo.comGamesRunte IncNov 1236
9Emily BartolettiEmily.Bartoletti@gmail.comAutomotiveRau - O'HaraJan 556
10Delores ReynoldsDelores.Reynolds@yahoo.comIndustrialRunte IncJun 420
No records

Here’s the code for the example above:

'use client';

import { DataTable } from 'mantine-datatable';
import dayjs from 'dayjs';
import { employees } from '~/data';

const records = employees.slice(0, 10);

export function ColumnPropertiesExample() {
  return (
    <DataTable
      withTableBorder
      withColumnBorders
      striped
      records={records}
      columns={[
        {
          accessor: 'index',
          title: '#',
          textAlign: '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,
          textAlign: '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'),
        },
      ]}
    />
  );
}

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:

Full name
Department name
Company
Age
Jerald HowellIndustrialRunte Inc74
Kathleen RueckerComputersShanahan, Robel and Beier80
Erica VolkmanToysGoyette Inc69
Clifford OberbrunnerAutomotiveRau - O'Hara45
Alison KlingJeweleryGoyette Inc27
Sue ZiemeBooksCummerata - Kuhlman64
Felicia GleasonShoesDoyle, Hodkiewicz and O'Connell47
Alfredo ZemlakGamesRunte Inc36
Emily BartolettiAutomotiveRau - O'Hara56
Delores ReynoldsIndustrialRunte Inc20
10 employees
6 companies
Avg: 52
No records

Here’s the code for the example above:

<DataTable
  withTableBorder
  withColumnBorders
  striped
  records={records}
  columns={[
    {
      accessor: 'name',
      title: 'Full name',
      render: ({ firstName, lastName }) => `${firstName} ${lastName}`,
      width: 160,
      // 👇 this column has a footer
      footer: (
        <Group gap="xs">
          <Box mb={-4}>
            <IconSum size="1.25em" />
          </Box>
          <div>{records.length} employees</div>
        </Group>
      ),
    },
    // 👇 this column has NO footer
    { accessor: 'department.name', width: 150 },
    {
      accessor: 'department.company.name',
      title: 'Company',
      width: 150,
      ellipsis: true,
      // 👇 this column has a footer
      footer: (
        <Group gap="xs">
          <Box mb={-4}>
            <IconSum size={16} />
          </Box>
          <div>{uniqBy(records, (record) => record.department.company.name).length} companies</div>
        </Group>
      ),
    },
    {
      accessor: 'age',
      width: 60,
      textAlign: 'right',
      visibleMediaQuery: (theme) => `(min-width: ${theme.breakpoints.xs})`,
      render: ({ birthDate }) => dayjs().diff(birthDate, 'years'),
      // 👇 this column has a footer
      footer: `Avg: ${Math.round(
        records.map((record) => dayjs().diff(record.birthDate, 'years')).reduce((a, b) => a + b, 0) / records.length
      )}`,
    },
  ]}
/>

Styling column titles, cells and footers

In addition, each column can be further customized by specifying the following styling properties:

  • titleClassName: string
    A custom class name for the column title.
  • titleStyle
    A custom style object for the column title, or a function accepting the current theme and returning a style object.
  • cellsClassName: string
    A function that accepts the current record as its argument and returns a string representing a custom class name for the column cells.
  • cellsStyle
    A function that accepts the current record as its argument and returns either a style object for the column cells, or a function accepting the current theme and returning a style object.
  • footerClassName: string
    A custom class name for the column footer.
  • footerStyle
    A custom style object for the column footer, or a function accepting the current theme and returning a style object.

Consider this example:

#
Full name
Email
Department name
Company
Birthday
Age
1Jerald HowellJerald.Howell32@yahoo.comIndustrialRunte IncMay 2174
2Kathleen RueckerKathleen_Ruecker@hotmail.comComputersShanahan, Robel and BeierDec 1980
3Erica VolkmanErica.Volkman37@gmail.comToysGoyette IncJan 2969
4Clifford OberbrunnerClifford.Oberbrunner@hotmail.comAutomotiveRau - O'HaraMar 645
5Alison KlingAlison16@gmail.comJeweleryGoyette IncJan 2727
6Sue ZiemeSue.Zieme29@hotmail.comBooksCummerata - KuhlmanSep 1264
7Felicia GleasonFelicia30@yahoo.comShoesDoyle, Hodkiewicz and O'ConnellMar 947
8Alfredo ZemlakAlfredo22@yahoo.comGamesRunte IncNov 1236
9Emily BartolettiEmily.Bartoletti@gmail.comAutomotiveRau - O'HaraJan 556
10Delores ReynoldsDelores.Reynolds@yahoo.comIndustrialRunte IncJun 420
10 employees6 companiesAvg: 52
No records

Here’s the code for the example above:

'use client';

import { rgba } from '@mantine/core';
import { DataTable, uniqBy } from 'mantine-datatable';
import clsx from 'clsx';
import dayjs from 'dayjs';
import { employees } from '~/data';
import classes from './ColumnStylingExample.module.css';

const records = employees.slice(0, 10);

export function ColumnStylingExample() {
  return (
    <DataTable
      withTableBorder
      withColumnBorders
      striped
      records={records}
      columns={[
        {
          accessor: 'index',
          title: '#',
          textAlign: '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 function returning a style object
          //    this function receives the current record as its argument, but we're not using it here
          cellsStyle: () => ({ fontStyle: 'italic' }),
          // 👇 style cells with a class name depending on current record
          cellsClassName: ({ sex }) => clsx({ [classes.male]: sex === 'male', [classes.female]: sex === 'female' }),
          footer: `${records.length} employees`,
          // style footer with a style object
          footerStyle: { fontStyle: 'italic' },
          render: ({ firstName, lastName }) => `${firstName} ${lastName}`,
        },
        { accessor: 'email' },
        {
          accessor: 'department.name',
          width: 150,
          // 👇 style title with a function returning a style object
          titleStyle: (theme) => ({ color: theme.colors.green[6] }),
          // 👇 style cells with a function returning a style function
          cellsStyle: () => (theme) => ({
            color: theme.colors.green[8],
            background: 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 a function returning a style object
          footerStyle: (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 function accepting the current record and returning another
          //    function that accepts the current theme and returns a style object
          //    (i.e. people born in winter will have their birthday in blue)
          cellsStyle:
            ({ birthDate }) =>
            (theme) => ({
              color: ['Dec', 'Jan', 'Feb'].includes(dayjs(birthDate).format('MMM')) ? theme.colors.blue[6] : undefined,
            }),
          render: ({ birthDate }) => dayjs(birthDate).format('MMM D'),
        },
        {
          accessor: 'age',
          width: 80,
          textAlign: 'right',
          // 👇 style title with a style object
          titleStyle: { fontStyle: 'italic' },
          // 👇 style cells depending on current record, with a function returning a style object
          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,
        },
      ]}
    />
  );
}

Head over to the next example to discover more features.