Docs
CodeRabbit
Cloudflare
AG Grid
SerpAPI
Netlify
OpenRouter
WorkOS
Clerk
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
CodeRabbit
Cloudflare
AG Grid
SerpAPI
Netlify
OpenRouter
WorkOS
Clerk
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
Table API Reference
Column API Reference
Row API Reference
Cell API Reference
Header API Reference
Features API Reference
Static Functions API Reference
Legacy API Reference

React Example: Lib Mantine

import * as React from 'react'
import ReactDOM from 'react-dom/client'
import {
  Button,
  Center,
  Container,
  Group,
  MantineProvider,
  Pagination,
  Paper,
  Select,
  Stack,
  Table,
  Text,
  TextInput,
  UnstyledButton,
} from '@mantine/core'
import '@mantine/core/styles.css'
import {
  IconArrowDown,
  IconArrowUp,
  IconSearch,
  IconSelector,
} from '@tabler/icons-react'
import {
  createColumnHelper,
  createFilteredRowModel,
  createPaginatedRowModel,
  createSortedRowModel,
  filterFns,
  globalFilteringFeature,
  rowPaginationFeature,
  rowSortingFeature,
  sortFns,
  tableFeatures,
  useTable,
} from '@tanstack/react-table'
import { makeData } from './makeData'
import type { Person } from './makeData'
import './index.css'

const _features = tableFeatures({
  rowSortingFeature,
  rowPaginationFeature,
  globalFilteringFeature,
})

const columnHelper = createColumnHelper<typeof _features, Person>()
function getAriaSort(sortDirection: false | 'asc' | 'desc') {
  if (sortDirection === 'asc') return 'ascending'
  if (sortDirection === 'desc') return 'descending'
  return 'none'
}

const columns = columnHelper.columns([
  columnHelper.accessor('firstName', {
    header: 'First Name',
    cell: (info) => info.getValue(),
  }),
  columnHelper.accessor((row) => row.lastName, {
    id: 'lastName',
    header: 'Last Name',
    cell: (info) => (
      <Text span fs="italic">
        {info.getValue<string>()}
      </Text>
    ),
  }),
  columnHelper.accessor((row) => Number(row.age), {
    id: 'age',
    header: 'Age',
    cell: (info) => info.renderValue(),
  }),
  columnHelper.accessor('visits', {
    header: 'Visits',
  }),
  columnHelper.accessor('status', {
    header: 'Status',
  }),
  columnHelper.accessor('progress', {
    header: 'Profile Progress',
  }),
])

function App() {
  const [data, setData] = React.useState(() => makeData(200))
  const refreshData = () => setData(makeData(200))
  const stressTest = () => setData(makeData(10_000))

  const table = useTable(
    {
      debugTable: true,
      _features,
      _rowModels: {
        sortedRowModel: createSortedRowModel(sortFns),
        paginatedRowModel: createPaginatedRowModel(),
        filteredRowModel: createFilteredRowModel(filterFns),
      },
      columns,
      data,
      globalFilterFn: 'includesString',
    },
    (state) => state, // default selector
  )
  return (
    <Container size="lg" py="xl">
      <Stack gap="lg">
        <Group justify="space-between" align="center">
          <TextInput
            value={table.state.globalFilter ?? ''}
            onChange={(event) =>
              table.setGlobalFilter(event.currentTarget.value)
            }
            placeholder="Search all columns..."
            leftSection={<IconSearch size={16} />}
            w={{ base: '100%', sm: 360 }}
          />
          <Group gap="xs">
            <Button variant="outline" onClick={refreshData}>
              Regenerate Data
            </Button>
            <Button variant="outline" onClick={stressTest}>
              Stress Test (10k rows)
            </Button>
          </Group>
        </Group>

        <Paper withBorder>
          <Table.ScrollContainer minWidth={760}>
            <Table
              highlightOnHover
              withColumnBorders
              withRowBorders
              withTableBorder
            >
              <Table.Thead>
                {table.getHeaderGroups().map((headerGroup) => (
                  <Table.Tr key={headerGroup.id}>
                    {headerGroup.headers.map((header) => {
                      const sortDirection = header.column.getIsSorted()
                      const sortIcon =
                        sortDirection === 'asc' ? (
                          <IconArrowUp size={16} />
                        ) : sortDirection === 'desc' ? (
                          <IconArrowDown size={16} />
                        ) : (
                          <IconSelector size={16} opacity={0.45} />
                        )

                      return (
                        <Table.Th
                          key={header.id}
                          colSpan={header.colSpan}
                          aria-sort={getAriaSort(sortDirection || false)}
                          data-sort={sortDirection || undefined}
                        >
                          {header.isPlaceholder ? null : header.column.getCanSort() ? (
                            <UnstyledButton
                              onClick={header.column.getToggleSortingHandler()}
                              style={{ width: '100%' }}
                            >
                              <Group gap="xs" wrap="nowrap">
                                <Text fw={600}>
                                  <table.FlexRender header={header} />
                                </Text>
                                {sortIcon}
                              </Group>
                            </UnstyledButton>
                          ) : (
                            <Text fw={600}>
                              <table.FlexRender header={header} />
                            </Text>
                          )}
                        </Table.Th>
                      )
                    })}
                  </Table.Tr>
                ))}
              </Table.Thead>
              <Table.Tbody>
                {table.getRowModel().rows.length === 0 ? (
                  <Table.Tr>
                    <Table.Td colSpan={columns.length}>
                      <Center py="xl">No results.</Center>
                    </Table.Td>
                  </Table.Tr>
                ) : (
                  table.getRowModel().rows.map((row) => (
                    <Table.Tr key={row.id}>
                      {row.getAllCells().map((cell) => (
                        <Table.Td key={cell.id}>
                          <table.FlexRender cell={cell} />
                        </Table.Td>
                      ))}
                    </Table.Tr>
                  ))
                )}
              </Table.Tbody>
            </Table>
          </Table.ScrollContainer>

          <Group justify="space-between" p="sm">
            <Text size="sm" c="dimmed">
              {table.getPrePaginatedRowModel().rows.length.toLocaleString()}{' '}
              rows
            </Text>
            <Group gap="xs">
              <Select
                aria-label="Rows per page"
                value={String(table.state.pagination.pageSize)}
                data={['10', '20', '30', '40', '50']}
                w={90}
                onChange={(value) => {
                  table.setPageSize(Number(value))
                  table.setPageIndex(0)
                }}
              />
              <Pagination
                value={table.state.pagination.pageIndex + 1}
                total={table.getPageCount()}
                onChange={(page) => table.setPageIndex(page - 1)}
              />
            </Group>
          </Group>
        </Paper>
      </Stack>
    </Container>
  )
}

const rootElement = document.getElementById('root')
if (!rootElement) throw new Error('Failed to find the root element')

ReactDOM.createRoot(rootElement).render(
  <React.StrictMode>
    <MantineProvider forceColorScheme="light">
      <App />
    </MantineProvider>
  </React.StrictMode>,
)
import * as React from 'react'
import ReactDOM from 'react-dom/client'
import {
  Button,
  Center,
  Container,
  Group,
  MantineProvider,
  Pagination,
  Paper,
  Select,
  Stack,
  Table,
  Text,
  TextInput,
  UnstyledButton,
} from '@mantine/core'
import '@mantine/core/styles.css'
import {
  IconArrowDown,
  IconArrowUp,
  IconSearch,
  IconSelector,
} from '@tabler/icons-react'
import {
  createColumnHelper,
  createFilteredRowModel,
  createPaginatedRowModel,
  createSortedRowModel,
  filterFns,
  globalFilteringFeature,
  rowPaginationFeature,
  rowSortingFeature,
  sortFns,
  tableFeatures,
  useTable,
} from '@tanstack/react-table'
import { makeData } from './makeData'
import type { Person } from './makeData'
import './index.css'

const _features = tableFeatures({
  rowSortingFeature,
  rowPaginationFeature,
  globalFilteringFeature,
})

const columnHelper = createColumnHelper<typeof _features, Person>()
function getAriaSort(sortDirection: false | 'asc' | 'desc') {
  if (sortDirection === 'asc') return 'ascending'
  if (sortDirection === 'desc') return 'descending'
  return 'none'
}

const columns = columnHelper.columns([
  columnHelper.accessor('firstName', {
    header: 'First Name',
    cell: (info) => info.getValue(),
  }),
  columnHelper.accessor((row) => row.lastName, {
    id: 'lastName',
    header: 'Last Name',
    cell: (info) => (
      <Text span fs="italic">
        {info.getValue<string>()}
      </Text>
    ),
  }),
  columnHelper.accessor((row) => Number(row.age), {
    id: 'age',
    header: 'Age',
    cell: (info) => info.renderValue(),
  }),
  columnHelper.accessor('visits', {
    header: 'Visits',
  }),
  columnHelper.accessor('status', {
    header: 'Status',
  }),
  columnHelper.accessor('progress', {
    header: 'Profile Progress',
  }),
])

function App() {
  const [data, setData] = React.useState(() => makeData(200))
  const refreshData = () => setData(makeData(200))
  const stressTest = () => setData(makeData(10_000))

  const table = useTable(
    {
      debugTable: true,
      _features,
      _rowModels: {
        sortedRowModel: createSortedRowModel(sortFns),
        paginatedRowModel: createPaginatedRowModel(),
        filteredRowModel: createFilteredRowModel(filterFns),
      },
      columns,
      data,
      globalFilterFn: 'includesString',
    },
    (state) => state, // default selector
  )
  return (
    <Container size="lg" py="xl">
      <Stack gap="lg">
        <Group justify="space-between" align="center">
          <TextInput
            value={table.state.globalFilter ?? ''}
            onChange={(event) =>
              table.setGlobalFilter(event.currentTarget.value)
            }
            placeholder="Search all columns..."
            leftSection={<IconSearch size={16} />}
            w={{ base: '100%', sm: 360 }}
          />
          <Group gap="xs">
            <Button variant="outline" onClick={refreshData}>
              Regenerate Data
            </Button>
            <Button variant="outline" onClick={stressTest}>
              Stress Test (10k rows)
            </Button>
          </Group>
        </Group>

        <Paper withBorder>
          <Table.ScrollContainer minWidth={760}>
            <Table
              highlightOnHover
              withColumnBorders
              withRowBorders
              withTableBorder
            >
              <Table.Thead>
                {table.getHeaderGroups().map((headerGroup) => (
                  <Table.Tr key={headerGroup.id}>
                    {headerGroup.headers.map((header) => {
                      const sortDirection = header.column.getIsSorted()
                      const sortIcon =
                        sortDirection === 'asc' ? (
                          <IconArrowUp size={16} />
                        ) : sortDirection === 'desc' ? (
                          <IconArrowDown size={16} />
                        ) : (
                          <IconSelector size={16} opacity={0.45} />
                        )

                      return (
                        <Table.Th
                          key={header.id}
                          colSpan={header.colSpan}
                          aria-sort={getAriaSort(sortDirection || false)}
                          data-sort={sortDirection || undefined}
                        >
                          {header.isPlaceholder ? null : header.column.getCanSort() ? (
                            <UnstyledButton
                              onClick={header.column.getToggleSortingHandler()}
                              style={{ width: '100%' }}
                            >
                              <Group gap="xs" wrap="nowrap">
                                <Text fw={600}>
                                  <table.FlexRender header={header} />
                                </Text>
                                {sortIcon}
                              </Group>
                            </UnstyledButton>
                          ) : (
                            <Text fw={600}>
                              <table.FlexRender header={header} />
                            </Text>
                          )}
                        </Table.Th>
                      )
                    })}
                  </Table.Tr>
                ))}
              </Table.Thead>
              <Table.Tbody>
                {table.getRowModel().rows.length === 0 ? (
                  <Table.Tr>
                    <Table.Td colSpan={columns.length}>
                      <Center py="xl">No results.</Center>
                    </Table.Td>
                  </Table.Tr>
                ) : (
                  table.getRowModel().rows.map((row) => (
                    <Table.Tr key={row.id}>
                      {row.getAllCells().map((cell) => (
                        <Table.Td key={cell.id}>
                          <table.FlexRender cell={cell} />
                        </Table.Td>
                      ))}
                    </Table.Tr>
                  ))
                )}
              </Table.Tbody>
            </Table>
          </Table.ScrollContainer>

          <Group justify="space-between" p="sm">
            <Text size="sm" c="dimmed">
              {table.getPrePaginatedRowModel().rows.length.toLocaleString()}{' '}
              rows
            </Text>
            <Group gap="xs">
              <Select
                aria-label="Rows per page"
                value={String(table.state.pagination.pageSize)}
                data={['10', '20', '30', '40', '50']}
                w={90}
                onChange={(value) => {
                  table.setPageSize(Number(value))
                  table.setPageIndex(0)
                }}
              />
              <Pagination
                value={table.state.pagination.pageIndex + 1}
                total={table.getPageCount()}
                onChange={(page) => table.setPageIndex(page - 1)}
              />
            </Group>
          </Group>
        </Paper>
      </Stack>
    </Container>
  )
}

const rootElement = document.getElementById('root')
if (!rootElement) throw new Error('Failed to find the root element')

ReactDOM.createRoot(rootElement).render(
  <React.StrictMode>
    <MantineProvider forceColorScheme="light">
      <App />
    </MantineProvider>
  </React.StrictMode>,
)