Advanced Example
Here is a more advanced example showcasing Mantine React Table's many features. Features such as row selection, expanding detail panels, header groups, column ordering, column pinning, column grouping, custom column and cell renders, etc., can be seen here.
This example is still only using client-side features. If you want to see an example of how to use Mantine React Table with server side logic and remote data, check out either the Remote Data Example or the React-Query Example.
Employee | Job Info | ||||||
---|---|---|---|---|---|---|---|
Actions | Name | Email | Salary | Job Title | Start Date | ||
Joseph Hand | $57,752 | Customer Directives Architect | 12/28/2022 | ||||
Signature Catch Phrase:"Front-line logistical service-desk" | |||||||
Paula Kohler | $47,029 | Direct Configuration Agent | 1/4/2023 | ||||
Signature Catch Phrase:"Stand-alone non-volatile encoding" | |||||||
Domenic Cassin | $55,602 | Corporate Operations Planner | 2/7/2022 | ||||
Signature Catch Phrase:"Digitized asynchronous definition" | |||||||
Rey Runte | $88,782 | Direct Optimization Manager | 10/2/2022 | ||||
Signature Catch Phrase:"Automated next generation knowledge base" | |||||||
Buck Mosciski | $95,101 | Internal Mobility Orchestrator | 11/14/2022 | ||||
Signature Catch Phrase:"Ameliorated clear-thinking capacity" | |||||||
Johnson Nitzsche | $96,104 | Lead Accounts Director | 8/12/2022 | ||||
Signature Catch Phrase:"Down-sized scalable application" | |||||||
Silas Hermiston | $64,532 | International Operations Consultant | 6/3/2022 | ||||
Signature Catch Phrase:"Face to face grid-enabled encryption" | |||||||
Kailey Bergstrom | $26,096 | Regional Web Planner | 10/17/2022 | ||||
Signature Catch Phrase:"Implemented bottom-line algorithm" | |||||||
Lilian Tromp | $72,692 | Central Implementation Orchestrator | 9/13/2022 | ||||
Signature Catch Phrase:"Open-architected tangible moderator" | |||||||
Maxine Schmidt | $89,317 | Principal Communications Orchestrator | 7/1/2022 | ||||
Signature Catch Phrase:"Focused intermediate groupware" |
1import { useMemo } from 'react';2import {3MantineReactTable,4useMantineReactTable,5type MRT_ColumnDef,6MRT_GlobalFilterTextInput,7MRT_ToggleFiltersButton,8} from 'mantine-react-table';9import { Box, Button, Flex, Menu, Text, Title } from '@mantine/core';10import { IconUserCircle, IconSend } from '@tabler/icons-react';11import { data } from './makeData';1213export type Employee = {14firstName: string;15lastName: string;16email: string;17jobTitle: string;18salary: number;19startDate: string;20signatureCatchPhrase: string;21avatar: string;22};2324const Example = () => {25const columns = useMemo<MRT_ColumnDef<Employee>[]>(26() => [27{28id: 'employee', //id used to define `group` column29header: 'Employee',30columns: [31{32accessorFn: (row) => `${row.firstName} ${row.lastName}`, //accessorFn used to join multiple data into a single cell33id: 'name', //id is still required when using accessorFn instead of accessorKey34header: 'Name',35size: 250,36filterVariant: 'autocomplete',37Cell: ({ renderedCellValue, row }) => (38<Box39sx={{40display: 'flex',41alignItems: 'center',42gap: '16px',43}}44>45<img46alt="avatar"47height={30}48src={row.original.avatar}49style={{ borderRadius: '50%' }}50/>51<span>{renderedCellValue}</span>52</Box>53),54},55{56accessorKey: 'email', //accessorKey used to define `data` column. `id` gets set to accessorKey automatically57enableClickToCopy: true,58header: 'Email',59size: 300,60},61],62},63{64id: 'id',65header: 'Job Info',66columns: [67{68accessorKey: 'salary',69header: 'Salary',70size: 200,71filterVariant: 'range-slider',72mantineFilterRangeSliderProps: {73color: 'indigo',74label: (value) =>75value?.toLocaleString?.('en-US', {76style: 'currency',77currency: 'USD',78minimumFractionDigits: 0,79maximumFractionDigits: 0,80}),81},82//custom conditional format and styling83Cell: ({ cell }) => (84<Box85sx={(theme) => ({86backgroundColor:87cell.getValue<number>() < 50_00088? theme.colors.red[9]89: cell.getValue<number>() >= 50_000 &&90cell.getValue<number>() < 75_00091? theme.colors.yellow[9]92: theme.colors.green[9],93borderRadius: '4px',94color: '#fff',95maxWidth: '9ch',96padding: '4px',97})}98>99{cell.getValue<number>()?.toLocaleString?.('en-US', {100style: 'currency',101currency: 'USD',102minimumFractionDigits: 0,103maximumFractionDigits: 0,104})}105</Box>106),107},108{109accessorKey: 'jobTitle', //hey a simple column for once110header: 'Job Title',111filterVariant: 'multi-select',112size: 350,113},114{115accessorFn: (row) => {116//convert to Date for sorting and filtering117const sDay = new Date(row.startDate);118sDay.setHours(0, 0, 0, 0); // remove time from date (useful if filter by equals exact date)119return sDay;120},121id: 'startDate',122header: 'Start Date',123filterVariant: 'date-range',124sortingFn: 'datetime',125enableColumnFilterModes: false, //keep this as only date-range filter with between inclusive filterFn126Cell: ({ cell }) => cell.getValue<Date>()?.toLocaleDateString(), //render Date as a string127Header: ({ column }) => <em>{column.columnDef.header}</em>, //custom header markup128},129],130},131],132[],133);134135const table = useMantineReactTable({136columns,137data, //must be memoized or stable (useState, useMemo, defined outside of this component, etc.)138enableColumnFilterModes: true,139enableColumnOrdering: true,140enableFacetedValues: true,141enableGrouping: true,142enablePinning: true,143enableRowActions: true,144enableRowSelection: true,145initialState: { showColumnFilters: true, showGlobalFilter: true },146paginationDisplayMode: 'pages',147positionToolbarAlertBanner: 'bottom',148mantinePaginationProps: {149radius: 'xl',150size: 'lg',151},152mantineSearchTextInputProps: {153placeholder: 'Search Employees',154},155renderDetailPanel: ({ row }) => (156<Box157sx={{158display: 'flex',159justifyContent: 'flex-start',160alignItems: 'center',161gap: '16px',162padding: '16px',163}}164>165<img166alt="avatar"167height={200}168src={row.original.avatar}169style={{ borderRadius: '50%' }}170/>171<Box sx={{ textAlign: 'center' }}>172<Title>Signature Catch Phrase:</Title>173<Text>"{row.original.signatureCatchPhrase}"</Text>174</Box>175</Box>176),177renderRowActionMenuItems: () => (178<>179<Menu.Item icon={<IconUserCircle />}>View Profile</Menu.Item>180<Menu.Item icon={<IconSend />}>Send Email</Menu.Item>181</>182),183renderTopToolbar: ({ table }) => {184const handleDeactivate = () => {185table.getSelectedRowModel().flatRows.map((row) => {186alert('deactivating ' + row.getValue('name'));187});188};189190const handleActivate = () => {191table.getSelectedRowModel().flatRows.map((row) => {192alert('activating ' + row.getValue('name'));193});194};195196const handleContact = () => {197table.getSelectedRowModel().flatRows.map((row) => {198alert('contact ' + row.getValue('name'));199});200};201202return (203<Flex p="md" justify="space-between">204<Flex gap="xs">205{/* import MRT sub-components */}206<MRT_GlobalFilterTextInput table={table} />207<MRT_ToggleFiltersButton table={table} />208</Flex>209<Flex sx={{ gap: '8px' }}>210<Button211color="red"212disabled={!table.getIsSomeRowsSelected()}213onClick={handleDeactivate}214variant="filled"215>216Deactivate217</Button>218<Button219color="green"220disabled={!table.getIsSomeRowsSelected()}221onClick={handleActivate}222variant="filled"223>224Activate225</Button>226<Button227color="blue"228disabled={!table.getIsSomeRowsSelected()}229onClick={handleContact}230variant="filled"231>232Contact233</Button>234</Flex>235</Flex>236);237},238});239240return <MantineReactTable table={table} />;241};242243export default Example;
1import { useMemo } from 'react';2import {3MRT_GlobalFilterTextInput,4MRT_ToggleFiltersButton,5MantineReactTable,6useMantineReactTable,7} from 'mantine-react-table';8import { Box, Button, Flex, Menu, Text, Title } from '@mantine/core';9import { IconUserCircle, IconSend } from '@tabler/icons-react';10import { data } from './makeData';1112const Example = () => {13const columns = useMemo(14() => [15{16id: 'employee', //id used to define `group` column17header: 'Employee',18columns: [19{20accessorFn: (row) => `${row.firstName} ${row.lastName}`, //accessorFn used to join multiple data into a single cell21id: 'name', //id is still required when using accessorFn instead of accessorKey22header: 'Name',23size: 250,24filterVariant: 'autocomplete',25Cell: ({ renderedCellValue, row }) => (26<Box27sx={{28display: 'flex',29alignItems: 'center',30gap: '16px',31}}32>33<img34alt="avatar"35height={30}36src={row.original.avatar}37style={{ borderRadius: '50%' }}38/>39<span>{renderedCellValue}</span>40</Box>41),42},43{44accessorKey: 'email', //accessorKey used to define `data` column. `id` gets set to accessorKey automatically45enableClickToCopy: true,46header: 'Email',47size: 300,48},49],50},51{52id: 'id',53header: 'Job Info',54columns: [55{56accessorKey: 'salary',57header: 'Salary',58size: 200,59filterVariant: 'range-slider',60mantineFilterRangeSliderProps: {61color: 'indigo',62label: (value) =>63value?.toLocaleString?.('en-US', {64style: 'currency',65currency: 'USD',66minimumFractionDigits: 0,67maximumFractionDigits: 0,68}),69},70//custom conditional format and styling71Cell: ({ cell }) => (72<Box73sx={(theme) => ({74backgroundColor:75cell.getValue() < 50_00076? theme.colors.red[9]77: cell.getValue() >= 50_000 && cell.getValue() < 75_00078? theme.colors.yellow[9]79: theme.colors.green[9],80borderRadius: '4px',81color: '#fff',82maxWidth: '9ch',83padding: '4px',84})}85>86{cell.getValue()?.toLocaleString?.('en-US', {87style: 'currency',88currency: 'USD',89minimumFractionDigits: 0,90maximumFractionDigits: 0,91})}92</Box>93),94},95{96accessorKey: 'jobTitle', //hey a simple column for once97header: 'Job Title',98filterVariant: 'multi-select',99size: 350,100},101{102accessorFn: (row) => {103//convert to Date for sorting and filtering104const sDay = new Date(row.startDate);105sDay.setHours(0, 0, 0, 0); // remove time from date (useful if filter by equals exact date)106return sDay;107},108id: 'startDate',109header: 'Start Date',110filterVariant: 'date-range',111sortingFn: 'datetime',112enableColumnFilterModes: false, //keep this as only date-range filter with between inclusive filterFn113Cell: ({ cell }) => cell.getValue()?.toLocaleDateString(), //render Date as a string114Header: ({ column }) => <em>{column.columnDef.header}</em>, //custom header markup115},116],117},118],119[],120);121122const table = useMantineReactTable({123columns,124data, //must be memoized or stable (useState, useMemo, defined outside of this component, etc.)125enableColumnFilterModes: true,126enableColumnOrdering: true,127enableFacetedValues: true,128enableGrouping: true,129enablePinning: true,130enableRowActions: true,131enableRowSelection: true,132initialState: { showColumnFilters: true, showGlobalFilter: true },133paginationDisplayMode: 'pages',134positionToolbarAlertBanner: 'bottom',135mantinePaginationProps: {136radius: 'xl',137size: 'lg',138},139mantineSearchTextInputProps: {140placeholder: 'Search Employees',141},142renderDetailPanel: ({ row }) => (143<Box144sx={{145display: 'flex',146justifyContent: 'flex-start',147alignItems: 'center',148gap: '16px',149padding: '16px',150}}151>152<img153alt="avatar"154height={200}155src={row.original.avatar}156style={{ borderRadius: '50%' }}157/>158<Box sx={{ textAlign: 'center' }}>159<Title>Signature Catch Phrase:</Title>160<Text>"{row.original.signatureCatchPhrase}"</Text>161</Box>162</Box>163),164renderRowActionMenuItems: () => (165<>166<Menu.Item icon={<IconUserCircle />}>View Profile</Menu.Item>167<Menu.Item icon={<IconSend />}>Send Email</Menu.Item>168</>169),170renderTopToolbar: ({ table }) => {171const handleDeactivate = () => {172table.getSelectedRowModel().flatRows.map((row) => {173alert('deactivating ' + row.getValue('name'));174});175};176177const handleActivate = () => {178table.getSelectedRowModel().flatRows.map((row) => {179alert('activating ' + row.getValue('name'));180});181};182183const handleContact = () => {184table.getSelectedRowModel().flatRows.map((row) => {185alert('contact ' + row.getValue('name'));186});187};188189return (190<Flex p="md" justify="space-between">191<Flex gap="xs">192{/* import MRT sub-components */}193<MRT_GlobalFilterTextInput table={table} />194<MRT_ToggleFiltersButton table={table} />195</Flex>196<Flex sx={{ gap: '8px' }}>197<Button198color="red"199disabled={!table.getIsSomeRowsSelected()}200onClick={handleDeactivate}201variant="filled"202>203Deactivate204</Button>205<Button206color="green"207disabled={!table.getIsSomeRowsSelected()}208onClick={handleActivate}209variant="filled"210>211Activate212</Button>213<Button214color="blue"215disabled={!table.getIsSomeRowsSelected()}216onClick={handleContact}217variant="filled"218>219Contact220</Button>221</Flex>222</Flex>223);224},225});226227return <MantineReactTable table={table} />;228};229230export default Example;
1import { useMemo } from 'react';2import { MantineReactTable, type MRT_ColumnDef } from 'mantine-react-table';3import { Box, Button, Menu, Text, Title } from '@mantine/core';4import { IconUserCircle, IconSend } from '@tabler/icons-react';5import { data } from './makeData';67export type Employee = {8firstName: string;9lastName: string;10email: string;11jobTitle: string;12salary: number;13startDate: string;14signatureCatchPhrase: string;15avatar: string;16};1718const Example = () => {19const columns = useMemo<MRT_ColumnDef<Employee>[]>(20() => [21{22id: 'employee', //id used to define `group` column23header: 'Employee',24columns: [25{26accessorFn: (row) => `${row.firstName} ${row.lastName}`, //accessorFn used to join multiple data into a single cell27id: 'name', //id is still required when using accessorFn instead of accessorKey28header: 'Name',29size: 250,30filterVariant: 'autocomplete',31Cell: ({ renderedCellValue, row }) => (32<Box33sx={{34display: 'flex',35alignItems: 'center',36gap: '16px',37}}38>39<img40alt="avatar"41height={30}42src={row.original.avatar}43style={{ borderRadius: '50%' }}44/>45<span>{renderedCellValue}</span>46</Box>47),48},49{50accessorKey: 'email', //accessorKey used to define `data` column. `id` gets set to accessorKey automatically51enableClickToCopy: true,52header: 'Email',53size: 300,54},55],56},57{58id: 'id',59header: 'Job Info',60columns: [61{62accessorKey: 'salary',63header: 'Salary',64size: 200,65filterVariant: 'range-slider',66mantineFilterRangeSliderProps: {67color: 'indigo',68label: (value) =>69value?.toLocaleString?.('en-US', {70style: 'currency',71currency: 'USD',72minimumFractionDigits: 0,73maximumFractionDigits: 0,74}),75},76//custom conditional format and styling77Cell: ({ cell }) => (78<Box79sx={(theme) => ({80backgroundColor:81cell.getValue<number>() < 50_00082? theme.colors.red[9]83: cell.getValue<number>() >= 50_000 &&84cell.getValue<number>() < 75_00085? theme.colors.yellow[9]86: theme.colors.green[9],87borderRadius: '4px',88color: '#fff',89maxWidth: '9ch',90padding: '4px',91})}92>93{cell.getValue<number>()?.toLocaleString?.('en-US', {94style: 'currency',95currency: 'USD',96minimumFractionDigits: 0,97maximumFractionDigits: 0,98})}99</Box>100),101},102{103accessorKey: 'jobTitle', //hey a simple column for once104header: 'Job Title',105filterVariant: 'multi-select',106size: 350,107},108{109accessorFn: (row) => {110//convert to Date for sorting and filtering111const sDay = new Date(row.startDate);112sDay.setHours(0, 0, 0, 0); // remove time from date (useful if filter by equals exact date)113return sDay;114},115id: 'startDate',116header: 'Start Date',117filterVariant: 'date-range',118sortingFn: 'datetime',119enableColumnFilterModes: false, //keep this as only date-range filter with between inclusive filterFn120Cell: ({ cell }) => cell.getValue<Date>()?.toLocaleDateString(), //render Date as a string121Header: ({ column }) => <em>{column.columnDef.header}</em>, //custom header markup122},123],124},125],126[],127);128129return (130<MantineReactTable131columns={columns}132data={data}133enableColumnFilterModes134enableColumnOrdering135enableGrouping136enablePinning137enableRowActions138enableRowSelection139initialState={{ showColumnFilters: true }}140paginationDisplayMode="pages"141positionToolbarAlertBanner="bottom"142renderDetailPanel={({ row }) => (143<Box144sx={{145display: 'flex',146justifyContent: 'flex-start',147alignItems: 'center',148gap: '16px',149padding: '16px',150}}151>152<img153alt="avatar"154height={200}155src={row.original.avatar}156style={{ borderRadius: '50%' }}157/>158<Box sx={{ textAlign: 'center' }}>159<Title>Signature Catch Phrase:</Title>160<Text>"{row.original.signatureCatchPhrase}"</Text>161</Box>162</Box>163)}164renderRowActionMenuItems={() => (165<>166<Menu.Item icon={<IconUserCircle />}>View Profile</Menu.Item>167<Menu.Item icon={<IconSend />}>Send Email</Menu.Item>168</>169)}170renderTopToolbarCustomActions={({ table }) => {171const handleDeactivate = () => {172table.getSelectedRowModel().flatRows.map((row) => {173alert('deactivating ' + row.getValue('name'));174});175};176177const handleActivate = () => {178table.getSelectedRowModel().flatRows.map((row) => {179alert('activating ' + row.getValue('name'));180});181};182183const handleContact = () => {184table.getSelectedRowModel().flatRows.map((row) => {185alert('contact ' + row.getValue('name'));186});187};188189return (190<div style={{ display: 'flex', gap: '8px' }}>191<Button192color="red"193disabled={!table.getIsSomeRowsSelected()}194onClick={handleDeactivate}195variant="filled"196>197Deactivate198</Button>199<Button200color="green"201disabled={!table.getIsSomeRowsSelected()}202onClick={handleActivate}203variant="filled"204>205Activate206</Button>207<Button208color="blue"209disabled={!table.getIsSomeRowsSelected()}210onClick={handleContact}211variant="filled"212>213Contact214</Button>215</div>216);217}}218/>219);220};221222export default Example;
View Extra Storybook Examples