42

MC Data Table

A table component with sorting, row selection, search, and pagination.

Overview

The mc-data-table component is a composable data table for Micro Club UI, built on TanStack Table v9.

It includes:

  • McDataTable which takes columns and data and handles sorting, row selection, search, and pagination
  • Card chrome (McDataTableCard, McDataTableHeader, McDataTableToolbar) for the title, actions, tabs, and search above the table
  • McDataTableEmpty for empty, no-results, and error states
  • McDataTableCellUser for the common avatar + title + subtitle cell
  • Table primitives (McTable, McTableRow, McTableCell, …) for static tables that need no logic

Preview

Loading...
'use client';import * as React from 'react';import { PencilIcon, Trash2Icon } from 'lucide-react';import { McAvatar, McAvatarFallback } from '@/components/ui/mc-avatar';import { McBadge } from '@/components/ui/mc-badge';import { McButton } from '@/components/ui/mc-button';import {  createMcDataTableColumnHelper,  McDataTable,  McDataTableCard,  McDataTableCellUser,  McDataTableHeader,} from '@/components/ui/mc-data-table';type Member = {  id: string;  name: string;  username: string;  status: 'Active' | 'Inactive';  role: string;  email: string;  teams: string[];};const people = [  ['Olivia Rhye', 'Product Designer'],  ['Phoenix Baker', 'Product Manager'],  ['Lana Steiner', 'Frontend Developer'],  ['Demi Wilkinson', 'Backend Developer'],  ['Candice Wu', 'Fullstack Developer'],  ['Natali Craig', 'UX Designer'],  ['Drew Cano', 'UX Copywriter'],  ['Orlando Diggs', 'UI Designer'],  ['Andi Lane', 'Product Manager'],  ['Kate Morrison', 'QA Engineer'],];const members: Member[] = Array.from({ length: 50 }, (_, i) => {  const [name, role] = people[i % people.length];  const first = name.split(' ')[0].toLowerCase();  return {    id: String(i + 1),    name,    username: `@${first}`,    status: i % 7 === 3 ? 'Inactive' : 'Active',    role,    email: `${first}@microclub.info`,    teams: ['Design', 'Product', 'Marketing', 'Research', 'Ops'].slice(0, 2 + (i % 4)),  };});const teamColors = [  'bg-accent text-accent-foreground',  'bg-warning-foreground text-warning',  'bg-destructive-foreground text-destructive',];const helper = createMcDataTableColumnHelper<Member>();const columns = helper.columns([  helper.accessor('name', {    header: 'Name',    cell: ({ row }) => (      <McDataTableCellUser        avatar={          <McAvatar size="md">            <McAvatarFallback>              {row.original.name                .split(' ')                .map((part) => part[0])                .join('')}            </McAvatarFallback>          </McAvatar>        }        title={row.original.name}        subtitle={row.original.username}      />    ),  }),  helper.accessor('status', {    header: 'Status',    cell: ({ getValue }) => {      const active = getValue() === 'Active';      return (        <McBadge          className={active ? 'bg-success-foreground text-success' : 'bg-muted text-foreground'}        >          <span className="size-1.5 rounded-full bg-current" />          {getValue()}        </McBadge>      );    },  }),  helper.accessor('role', {    header: 'Role',    meta: { headerTooltip: 'The role this member holds in the club.' },  }),  helper.accessor('email', {    header: 'Email address',  }),  helper.accessor('teams', {    header: 'Teams',    enableSorting: false,    cell: ({ getValue }) => {      const teams = getValue();      return (        <div className="flex items-center gap-1">          {teams.slice(0, 3).map((team, i) => (            <McBadge key={team} className={teamColors[i]}>              {team}            </McBadge>          ))}          {teams.length > 3 && (            <McBadge className="bg-muted text-foreground">+{teams.length - 3}</McBadge>          )}        </div>      );    },  }),  helper.display({    id: 'actions',    cell: ({ row }) => (      <div className="flex items-center justify-end gap-1">        <McButton          variant="tertiary"          size="sm"          icon="only"          iconDefinition={<Trash2Icon className="size-4.5" />}          className="bg-transparent text-muted-foreground hover:bg-muted hover:text-foreground"          aria-label={`Delete ${row.original.name}`}        />        <McButton          variant="tertiary"          size="sm"          icon="only"          iconDefinition={<PencilIcon className="size-4.5" />}          className="bg-transparent text-muted-foreground hover:bg-muted hover:text-foreground"          aria-label={`Edit ${row.original.name}`}        />      </div>    ),  }),]);export default function McDataTableDemo() {  return (    <McDataTableCard>      <McDataTableHeader title="Team members" badge={`${members.length} users`} />      <McDataTable        columns={columns}        data={members}        getRowId={(member) => member.id}        enableRowSelection      />    </McDataTableCard>  );}

Add to Your Project

Deploy the mc-data-table directly to your components/ui directory:

npx mcoli-ui@latest add mc-data-table
pnpm dlx mcoli-ui@latest add mc-data-table
yarn dlx mcoli-ui@latest add mc-data-table
bunx mcoli-ui@latest add mc-data-table

This also installs mc-button, mc-checkbox, and mc-tooltip.

Manual Setup

If you prefer manual control:

Install the core dependencies
npm install @tanstack/react-table lucide-react
pnpm add @tanstack/react-table lucide-react
yarn add @tanstack/react-table lucide-react
bun add @tanstack/react-table lucide-react
Add mc-button, mc-checkbox, and mc-tooltip
Copy the source code
'use client';import * as React from 'react';import {  columnFilteringFeature,  createColumnHelper,  createFilteredRowModel,  createPaginatedRowModel,  createSortedRowModel,  filterFn_includesString,  flexRender,  globalFilteringFeature,  metaHelper,  rowPaginationFeature,  rowSelectionFeature,  rowSortingFeature,  sortFn_alphanumeric,  sortFn_basic,  sortFn_datetime,  sortFn_text,  tableFeatures,  useTable,  type ColumnDef,  type Header,  type OnChangeFn,  type RowData,  type RowSelectionState,  type Table,} from '@tanstack/react-table';import {  ArrowDownIcon,  ArrowLeftIcon,  ArrowRightIcon,  ArrowUpIcon,  HelpCircleIcon,} from 'lucide-react';import { cn } from '@/lib/utils';import { McButton } from '@/components/ui/mc-button';import { McCheckbox } from '@/components/ui/mc-checkbox';import { McTooltip, McTooltipContent, McTooltipTrigger } from '@/components/ui/mc-tooltip';// table primitivesfunction McTable({ className, ...props }: React.ComponentProps<'table'>) {  return (    <div data-slot="table-container" className="relative w-full overflow-x-auto">      <table        data-slot="table"        className={cn('w-full caption-bottom border-collapse text-sm', className)}        {...props}      />    </div>  );}function McTableHeader({ className, ...props }: React.ComponentProps<'thead'>) {  return (    <thead      data-slot="table-header"      className={cn('bg-muted/50 [&_tr]:border-b [&_tr]:hover:bg-transparent', className)}      {...props}    />  );}function McTableBody({ className, ...props }: React.ComponentProps<'tbody'>) {  return (    <tbody      data-slot="table-body"      className={cn('[&_tr:last-child]:border-0', className)}      {...props}    />  );}function McTableRow({ className, ...props }: React.ComponentProps<'tr'>) {  return (    <tr      data-slot="table-row"      className={cn(        'border-b border-border transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted',        className      )}      {...props}    />  );}function McTableHead({ className, ...props }: React.ComponentProps<'th'>) {  return (    <th      data-slot="table-head"      className={cn(        'h-11 px-6 py-3 text-left align-middle text-xs font-medium whitespace-nowrap text-muted-foreground',        className      )}      {...props}    />  );}function McTableCell({ className, ...props }: React.ComponentProps<'td'>) {  return (    <td      data-slot="table-cell"      className={cn(        'h-18 px-6 py-4 align-middle whitespace-nowrap text-muted-foreground',        className      )}      {...props}    />  );}function McTableCaption({ className, ...props }: React.ComponentProps<'caption'>) {  return (    <caption      data-slot="table-caption"      className={cn('mt-4 text-sm text-muted-foreground', className)}      {...props}    />  );}// card chromefunction McDataTableCard({ className, ...props }: React.ComponentProps<'div'>) {  return (    <div      data-slot="data-table-card"      className={cn(        'flex w-full flex-col overflow-hidden rounded-xl border border-border bg-card text-card-foreground shadow-xs',        className      )}      {...props}    />  );}function McDataTableHeader({  className,  title,  badge,  description,  actions,  ...props}: Omit<React.ComponentProps<'div'>, 'title'> & {  title: React.ReactNode;  badge?: React.ReactNode;  description?: React.ReactNode;  actions?: React.ReactNode;}) {  return (    <div      data-slot="data-table-header"      className={cn(        'flex flex-col gap-4 border-b border-border px-4 py-5 sm:flex-row sm:items-start sm:justify-between sm:px-6',        className      )}      {...props}    >      <div className="flex min-w-0 flex-col gap-1">        <div className="flex items-center gap-2">          <h3 className="text-lg font-semibold text-foreground">{title}</h3>          {badge && (            <span className="rounded-full bg-accent px-2 py-0.5 text-xs font-medium text-accent-foreground">              {badge}            </span>          )}        </div>        {description && <p className="text-sm text-muted-foreground">{description}</p>}      </div>      {actions && <div className="flex shrink-0 items-center gap-3">{actions}</div>}    </div>  );}function McDataTableToolbar({ className, ...props }: React.ComponentProps<'div'>) {  return (    <div      data-slot="data-table-toolbar"      className={cn(        'flex flex-col gap-3 border-b border-border px-4 py-3 md:flex-row md:items-center md:justify-between',        className      )}      {...props}    />  );}function McDataTableEmpty({  className,  icon,  title,  description,  actions,  ...props}: Omit<React.ComponentProps<'div'>, 'title'> & {  icon?: React.ReactNode;  title: React.ReactNode;  description?: React.ReactNode;  actions?: React.ReactNode;}) {  return (    <div      data-slot="data-table-empty"      className={cn(        'mx-auto flex max-w-sm flex-col items-center gap-4 px-4 py-10 text-center whitespace-normal',        className      )}      {...props}    >      {icon && (        <div className="flex size-12 items-center justify-center rounded-full bg-muted text-primary [&_svg]:size-6">          {icon}        </div>      )}      <div className="flex flex-col gap-1">        <p className="text-base font-semibold text-foreground">{title}</p>        {description && <p className="text-sm text-muted-foreground">{description}</p>}      </div>      {actions && <div className="flex items-center gap-3">{actions}</div>}    </div>  );}function McDataTableCellUser({  className,  avatar,  title,  subtitle,  ...props}: Omit<React.ComponentProps<'div'>, 'title'> & {  avatar?: React.ReactNode;  title: React.ReactNode;  subtitle?: React.ReactNode;}) {  return (    <div      data-slot="data-table-cell-user"      className={cn('flex items-center gap-3', className)}      {...props}    >      {avatar}      <div className="flex min-w-0 flex-col">        <span className="truncate text-sm font-medium text-foreground">{title}</span>        {subtitle && <span className="truncate text-sm text-muted-foreground">{subtitle}</span>}      </div>    </div>  );}// table logicexport interface McDataTableColumnMeta {  headerTooltip?: string;  headerClassName?: string;  cellClassName?: string;}const mcDataTableFeatures = tableFeatures({  rowSortingFeature,  sortedRowModel: createSortedRowModel(),  sortFns: {    alphanumeric: sortFn_alphanumeric,    basic: sortFn_basic,    datetime: sortFn_datetime,    text: sortFn_text,  },  columnFilteringFeature,  globalFilteringFeature,  filteredRowModel: createFilteredRowModel(),  filterFns: { includesString: filterFn_includesString },  rowSelectionFeature,  rowPaginationFeature,  paginatedRowModel: createPaginatedRowModel(),  columnMeta: metaHelper<McDataTableColumnMeta>(),});type McDataTableFeatures = typeof mcDataTableFeatures;// eslint-disable-next-line @typescript-eslint/no-explicit-anytype McDataTableColumnDef<TData extends RowData> = ColumnDef<McDataTableFeatures, TData, any>;function createMcDataTableColumnHelper<TData extends RowData>() {  return createColumnHelper<McDataTableFeatures, TData>();}type McDataTablePaginationVariant = 'numbered' | 'compact' | 'minimal';function McDataTableColumnHeader<TData extends RowData>({  header,}: {  header: Header<McDataTableFeatures, TData, unknown>;}) {  const { column } = header;  const label = flexRender(column.columnDef.header, header.getContext());  const tooltip = column.columnDef.meta?.headerTooltip;  const sorted = column.getIsSorted();  const help = tooltip && (    <McTooltip>      <McTooltipTrigger        aria-label="More information"        className="inline-flex text-muted-foreground hover:text-foreground"      >        <HelpCircleIcon className="size-4" />      </McTooltipTrigger>      <McTooltipContent description={tooltip} />    </McTooltip>  );  if (!column.getCanSort()) {    return (      <span className="inline-flex items-center gap-1">        {label}        {help}      </span>    );  }  const SortIcon = sorted === 'asc' ? ArrowUpIcon : ArrowDownIcon;  return (    <span className="inline-flex items-center gap-1">      <button        type="button"        onClick={column.getToggleSortingHandler()}        className={cn(          'group/sort inline-flex items-center gap-1 rounded-sm outline-none hover:text-foreground focus-visible:ring-4 focus-visible:ring-ring',          sorted && 'text-foreground'        )}      >        {label}        <SortIcon          className={cn(            'size-3.5 transition-opacity',            sorted ? 'opacity-100' : 'opacity-0 group-hover/sort:opacity-50'          )}        />      </button>      {help}    </span>  );}function getPageItems(page: number, pageCount: number): (number | 'ellipsis')[] {  if (pageCount <= 7) {    return Array.from({ length: pageCount }, (_, i) => i + 1);  }  if (page <= 3 || page >= pageCount - 2) {    return [1, 2, 3, 'ellipsis', pageCount - 2, pageCount - 1, pageCount];  }  return [1, 'ellipsis', page - 1, page, page + 1, 'ellipsis', pageCount];}function McDataTablePagination<TData extends RowData>({  table,  variant = 'numbered',  className,}: {  table: Table<McDataTableFeatures, TData>;  variant?: McDataTablePaginationVariant;  className?: string;}) {  const page = table.atoms.pagination.get().pageIndex + 1;  const pageCount = Math.max(table.getPageCount(), 1);  const canPrevious = table.getCanPreviousPage();  const canNext = table.getCanNextPage();  const pageLabel = (    <span className="text-sm font-medium text-foreground">      Page {page} of {pageCount}    </span>  );  if (variant === 'minimal') {    return (      <div        data-slot="data-table-pagination"        className={cn('flex items-center justify-between gap-3 px-4 py-3', className)}      >        <McButton          variant="secondary"          size="sm"          icon="only"          iconDefinition={<ArrowLeftIcon />}          aria-label="Go to previous page"          disabled={!canPrevious}          onClick={() => table.previousPage()}        />        {pageLabel}        <McButton          variant="secondary"          size="sm"          icon="only"          iconDefinition={<ArrowRightIcon />}          aria-label="Go to next page"          disabled={!canNext}          onClick={() => table.nextPage()}        />      </div>    );  }  if (variant === 'compact') {    return (      <div        data-slot="data-table-pagination"        className={cn('flex items-center justify-between gap-3 px-6 py-3', className)}      >        {pageLabel}        <div className="flex items-center gap-3">          <McButton            variant="secondary"            size="sm"            disabled={!canPrevious}            onClick={() => table.previousPage()}          >            Previous          </McButton>          <McButton            variant="secondary"            size="sm"            disabled={!canNext}            onClick={() => table.nextPage()}          >            Next          </McButton>        </div>      </div>    );  }  return (    <nav      aria-label="pagination"      data-slot="data-table-pagination"      className={cn('flex items-center justify-between gap-3 px-6 py-3', className)}    >      <McButton        variant="secondary"        size="sm"        icon="leading"        iconDefinition={<ArrowLeftIcon />}        disabled={!canPrevious}        onClick={() => table.previousPage()}      >        Previous      </McButton>      <span className="text-sm font-medium text-foreground md:hidden">        Page {page} of {pageCount}      </span>      <ul className="hidden items-center gap-0.5 md:flex">        {getPageItems(page, pageCount).map((item, i) => (          <li key={item === 'ellipsis' ? `ellipsis-${i}` : item}>            {item === 'ellipsis' ? (              <span                aria-hidden                className="flex size-10 items-center justify-center text-sm text-muted-foreground"              >                …              </span>            ) : (              <button                type="button"                aria-current={item === page ? 'page' : undefined}                onClick={() => table.setPageIndex(item - 1)}                className={cn(                  'flex size-10 items-center justify-center rounded-lg text-sm font-medium text-muted-foreground outline-none transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-4 focus-visible:ring-ring',                  item === page && 'bg-muted text-foreground'                )}              >                {item}              </button>            )}          </li>        ))}      </ul>      <McButton        variant="secondary"        size="sm"        icon="trailing"        iconDefinition={<ArrowRightIcon />}        disabled={!canNext}        onClick={() => table.nextPage()}      >        Next      </McButton>    </nav>  );}export interface McDataTableProps<TData extends RowData> {  columns: McDataTableColumnDef<TData>[];  data: TData[];  getRowId?: (row: TData, index: number) => string;  enableRowSelection?: boolean;  rowSelection?: RowSelectionState;  onRowSelectionChange?: OnChangeFn<RowSelectionState>;  globalFilter?: string;  pageSize?: number;  pagination?: McDataTablePaginationVariant | false;  empty?: React.ReactNode;  className?: string;}function McDataTable<TData extends RowData>({  columns,  data,  getRowId,  enableRowSelection = false,  rowSelection,  onRowSelectionChange,  globalFilter,  pageSize = 10,  pagination = 'numbered',  empty,  className,}: McDataTableProps<TData>) {  const table = useTable({    features: mcDataTableFeatures,    columns,    data,    getRowId,    enableRowSelection,    enableRowRangeSelection: false,    globalFilterFn: 'includesString',    manualPagination: pagination === false,    initialState: { pagination: { pageIndex: 0, pageSize } },    state: {      ...(globalFilter !== undefined && { globalFilter }),      ...(rowSelection !== undefined && { rowSelection }),    },    ...(onRowSelectionChange && { onRowSelectionChange }),  });  const rows = table.getRowModel().rows;  const columnCount = table.getAllLeafColumns().length;  return (    <div data-slot="data-table" className={cn('flex w-full flex-col', className)}>      <McTable>        <McTableHeader>          {table.getHeaderGroups().map((headerGroup) => (            <McTableRow key={headerGroup.id}>              {headerGroup.headers.map((header, index) => {                const sorted = header.column.getIsSorted();                return (                  <McTableHead                    key={header.id}                    colSpan={header.colSpan}                    aria-sort={                      sorted === 'asc' ? 'ascending' : sorted === 'desc' ? 'descending' : undefined                    }                    className={header.column.columnDef.meta?.headerClassName}                  >                    <div className="flex items-center gap-3">                      {enableRowSelection && index === 0 && (                        <McCheckbox                          aria-label="Select all rows on this page"                          checked={table.getIsAllPageRowsSelected()}                          indeterminate={                            !table.getIsAllPageRowsSelected() && table.getIsSomePageRowsSelected()                          }                          onCheckedChange={(checked) => table.toggleAllPageRowsSelected(checked)}                        />                      )}                      {!header.isPlaceholder && <McDataTableColumnHeader header={header} />}                    </div>                  </McTableHead>                );              })}            </McTableRow>          ))}        </McTableHeader>        <McTableBody>          {rows.length > 0 ? (            rows.map((row) => (              <McTableRow key={row.id} data-state={row.getIsSelected() ? 'selected' : undefined}>                {row.getAllCells().map((cell, index) => (                  <McTableCell key={cell.id} className={cell.column.columnDef.meta?.cellClassName}>                    {enableRowSelection && index === 0 ? (                      <div className="flex items-center gap-3">                        <McCheckbox                          aria-label="Select row"                          checked={row.getIsSelected()}                          disabled={!row.getCanSelect()}                          onCheckedChange={(checked) => row.toggleSelected(checked)}                        />                        {flexRender(cell.column.columnDef.cell, cell.getContext())}                      </div>                    ) : (                      flexRender(cell.column.columnDef.cell, cell.getContext())                    )}                  </McTableCell>                ))}              </McTableRow>            ))          ) : (            <McTableRow className="hover:bg-transparent">              <McTableCell colSpan={columnCount} className="h-auto p-0">                {empty ?? <McDataTableEmpty title="No results" />}              </McTableCell>            </McTableRow>          )}        </McTableBody>      </McTable>      {pagination && (        <McDataTablePagination          table={table}          variant={pagination}          className="border-t border-border"        />      )}    </div>  );}export {  McTable,  McTableHeader,  McTableBody,  McTableRow,  McTableHead,  McTableCell,  McTableCaption,  McDataTable,  McDataTableCard,  McDataTableHeader,  McDataTableToolbar,  McDataTableEmpty,  McDataTableCellUser,  McDataTableColumnHeader,  McDataTablePagination,  mcDataTableFeatures,  createMcDataTableColumnHelper,  type McDataTableFeatures,  type McDataTableColumnDef,  type McDataTablePaginationVariant,};
Adjust import paths

Ensure your @/lib/utils (or equivalent classname helper) is correctly configured.

Usage

Define columns with createMcDataTableColumnHelper, then pass them to McDataTable with your data.

import {
  createMcDataTableColumnHelper,
  McDataTable,
  McDataTableCard,
  McDataTableHeader,
} from '@/components/ui/mc-data-table';

type Member = { id: string; name: string; role: string };

const helper = createMcDataTableColumnHelper<Member>();

const columns = helper.columns([
  helper.accessor('name', { header: 'Name' }),
  helper.accessor('role', { header: 'Role' }),
]);

export default function Example({ members }: { members: Member[] }) {
  return (
    <McDataTableCard>
      <McDataTableHeader title="Team members" badge={`${members.length} users`} />
      <McDataTable columns={columns} data={members} getRowId={(member) => member.id} />
    </McDataTableCard>
  );
}

Keep columns outside the component (or memoize it) so the table does not rebuild on every render.

Examples

Custom cells

Use cell to render anything in a column. McDataTableCellUser covers the avatar + name + subtitle layout.

helper.accessor('name', {
  header: 'Name',
  cell: ({ row }) => (
    <McDataTableCellUser
      avatar={
        <McAvatar size="md">
          <McAvatarImage src={row.original.avatar} />
          <McAvatarFallback>OR</McAvatarFallback>
        </McAvatar>
      }
      title={row.original.name}
      subtitle={row.original.email}
    />
  ),
}),

Sorting

Every accessor column is sortable by default. Click a header to cycle ascending, descending, and unsorted. Turn it off per column with enableSorting: false.

helper.accessor('teams', { header: 'Teams', enableSorting: false }),

Header tooltip

Add a help icon with a tooltip next to a header through the column meta.

helper.accessor('role', {
  header: 'Role',
  meta: { headerTooltip: 'The role this member holds in the club.' },
}),

meta also accepts headerClassName and cellClassName to style a single column.

Row actions

Use a display column for per-row buttons.

helper.display({
  id: 'actions',
  cell: ({ row }) => (
    <McButton
      variant="tertiary"
      size="sm"
      icon="only"
      iconDefinition={<PencilIcon />}
      aria-label={`Edit ${row.original.name}`}
    />
  ),
}),

Row selection

enableRowSelection adds checkboxes to the first column. The header checkbox selects every row on the current page and shows an indeterminate state when only some are selected. Pass rowSelection and onRowSelectionChange to control the selection yourself.

const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});

<McDataTable
  columns={columns}
  data={members}
  getRowId={(member) => member.id}
  enableRowSelection
  rowSelection={rowSelection}
  onRowSelectionChange={setRowSelection}
/>;

Always pass getRowId with selection so selected rows stay stable when data is sorted, filtered, or reloaded.

Put tabs, search, and filter buttons in McDataTableToolbar, and pass the search text as globalFilter.

const [search, setSearch] = React.useState('');

<McDataTableCard>
  <McDataTableHeader
    title="Vendor movements"
    badge="240 vendors"
    description="Keep track of vendors and their security ratings."
    actions={<McButton size="sm">Add vendor</McButton>}
  />
  <McDataTableToolbar>
    <McTabs defaultValue="all">...</McTabs>
    <McInput
      placeholder="Search"
      value={search}
      onChange={(event) => setSearch(event.target.value)}
    />
  </McDataTableToolbar>
  <McDataTable columns={columns} data={vendors} globalFilter={search} pagination="compact" />
</McDataTableCard>;

Pagination styles

ValueFooter
"numbered"Previous, page numbers, Next
"compact""Page 1 of 10" with Previous and Next buttons
"minimal"Arrow buttons around "Page 1 of 10", for mobile
falseNo footer; every row is shown

Empty and error states

empty is rendered in place of the rows when there is no data or the search matches nothing. For a failed load, render McDataTableEmpty instead of the table.

<McDataTable
  columns={columns}
  data={vendors}
  globalFilter={search}
  empty={
    <McDataTableEmpty
      icon={<SearchIcon />}
      title="No vendors found"
      description={`Your search "${search}" did not match any vendors.`}
      actions={<McButton onClick={() => setSearch('')}>Clear search</McButton>}
    />
  }
/>

API Reference

McDataTable

PropTypeDefaultDescription
columnsMcDataTableColumnDef<TData>[]-Column definitions
dataTData[]-Rows to display
getRowId(row: TData, index: number) => stringrow indexStable id for each row
enableRowSelectionbooleanfalseShow selection checkboxes in the first column
rowSelectionRowSelectionStateundefinedControlled selection ({ [rowId]: true })
onRowSelectionChangeOnChangeFn<RowSelectionState>undefinedCalled when the selection changes
globalFilterstringundefinedSearch text matched against every column
pageSizenumber10Rows per page
pagination"numbered" | "compact" | "minimal" | false"numbered"Footer style, or false to show every row
emptyReact.ReactNode"No results"Rendered when there are no rows
classNamestringundefinedExtra classes for the wrapper

Column meta

FieldTypeDescription
headerTooltipstringShows a help icon with this tooltip text
headerClassNamestringExtra classes for this column's header cell
cellClassNamestringExtra classes for this column's body cells

McDataTableHeader

PropTypeDefaultDescription
titleReact.ReactNode-Table title
badgeReact.ReactNode-Count badge next to the title
descriptionReact.ReactNode-Supporting text under the title
actionsReact.ReactNode-Buttons aligned to the right

Also supports standard HTMLDivElement props.

McDataTableEmpty

PropTypeDefaultDescription
iconReact.ReactNode-Icon shown in a tinted circle
titleReact.ReactNode-Heading
descriptionReact.ReactNode-Supporting text
actionsReact.ReactNode-Buttons under the text

Also supports standard HTMLDivElement props.

McDataTableCellUser

PropTypeDefaultDescription
avatarReact.ReactNode-Avatar, logo, or file icon
titleReact.ReactNode-Primary text
subtitleReact.ReactNode-Secondary text

Other exports

  • McDataTableCard and McDataTableToolbar are styled div wrappers.
  • McDataTablePagination and McDataTableColumnHeader are the pieces McDataTable renders internally, exported for custom layouts.
  • McTable, McTableHeader, McTableBody, McTableRow, McTableHead, McTableCell, and McTableCaption are styled table elements with no logic.
  • mcDataTableFeatures is the TanStack feature set the table registers (sorting, filtering, selection, pagination). Use it with useTable if you need full control.

On this page