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 Hero Ui

import * as React from 'react'
import ReactDOM from 'react-dom/client'
import {
  Button,
  Input,
  ListBox,
  ListBoxItem,
  Pagination,
  Select,
  Table,
  cn,
} from '@heroui/react'
import {
  createColumnHelper,
  createFilteredRowModel,
  createPaginatedRowModel,
  createSortedRowModel,
  filterFns,
  globalFilteringFeature,
  rowPaginationFeature,
  rowSortingFeature,
  sortFns,
  tableFeatures,
  useTable,
} from '@tanstack/react-table'
import { makeData } from './makeData'
import type { Key, SortDescriptor } from '@heroui/react'
import type { SortingState } from '@tanstack/react-table'
import type { Person } from './makeData'
import './index.css'

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

const columnHelper = createColumnHelper<typeof _features, Person>()
function toSortDescriptor(sorting: SortingState): SortDescriptor | undefined {
  const sort = sorting[0]
  if (!sort) return undefined
  return {
    column: sort.id,
    direction: sort.desc ? 'descending' : 'ascending',
  }
}

function toSortingState(descriptor: SortDescriptor): SortingState {
  return [
    {
      id: String(descriptor.column),
      desc: descriptor.direction === 'descending',
    },
  ]
}

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) => <span className="italic">{info.getValue<string>()}</span>,
  }),
  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',
  }),
])

const pageSizeOptions = ['10', '20', '30', '40', '50']

function getPageItems(pageIndex: number, pageCount: number) {
  const currentPage = pageIndex + 1
  const pages = new Set<number>([
    1,
    pageCount,
    currentPage - 1,
    currentPage,
    currentPage + 1,
  ])

  return Array.from(pages)
    .filter((page) => page >= 1 && page <= pageCount)
    .sort((a, b) => a - b)
    .reduce<Array<number | 'ellipsis'>>((items, page) => {
      const previous = items[items.length - 1]
      if (typeof previous === 'number' && page - previous > 1) {
        items.push('ellipsis')
      }
      items.push(page)
      return items
    }, [])
}

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
  )

  const pageIndex = table.state.pagination.pageIndex
  const pageSize = table.state.pagination.pageSize
  const pageCount = table.getPageCount()
  const pageItems = getPageItems(pageIndex, pageCount)
  const rowCount = table.getPrePaginatedRowModel().rows.length
  const start = rowCount === 0 ? 0 : pageIndex * pageSize + 1
  const end = Math.min((pageIndex + 1) * pageSize, rowCount)

  return (
    <main className="mx-auto max-w-6xl px-6 py-8">
      <div className="flex flex-col gap-5">
        <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
          <Input
            aria-label="Search all columns"
            className="w-full sm:w-[360px]"
            placeholder="Search all columns..."
            value={table.state.globalFilter ?? ''}
            onChange={(event) =>
              table.setGlobalFilter(event.currentTarget.value)
            }
          />
          <div className="flex flex-wrap gap-2">
            <Button variant="secondary" onPress={refreshData}>
              Regenerate Data
            </Button>
            <Button variant="secondary" onPress={stressTest}>
              Stress Test (10k rows)
            </Button>
          </div>
        </div>

        <Table className="overflow-hidden rounded-lg border border-border">
          <Table.ScrollContainer>
            <Table.Content
              aria-label="Hero UI TanStack Table example"
              className="min-w-[760px]"
              sortDescriptor={toSortDescriptor(table.state.sorting)}
              onSortChange={(descriptor) =>
                table.setSorting(toSortingState(descriptor))
              }
            >
              <Table.Header>
                {table.getHeaderGroups()[0]?.headers.map((header) => (
                  <Table.Column
                    key={header.id}
                    id={header.column.id}
                    allowsSorting={header.column.getCanSort()}
                    isRowHeader={header.id === 'firstName'}
                  >
                    {({ sortDirection }) => (
                      <span className="flex items-center justify-between gap-2 font-semibold">
                        <table.FlexRender header={header} />
                        <span
                          aria-hidden="true"
                          className={cn(
                            'text-xs text-muted transition-transform',
                            sortDirection === 'descending' && 'rotate-180',
                            !sortDirection && 'opacity-40',
                          )}
                        >
                          {sortDirection ? '▲' : '↕'}
                        </span>
                      </span>
                    )}
                  </Table.Column>
                ))}
              </Table.Header>
              <Table.Body
                renderEmptyState={() => (
                  <div className="py-10 text-center text-sm text-muted">
                    No results.
                  </div>
                )}
              >
                {table.getRowModel().rows.map((row) => (
                  <Table.Row key={row.id} id={row.id}>
                    {row.getAllCells().map((cell) => (
                      <Table.Cell key={cell.id}>
                        <table.FlexRender cell={cell} />
                      </Table.Cell>
                    ))}
                  </Table.Row>
                ))}
              </Table.Body>
            </Table.Content>
          </Table.ScrollContainer>

          <Table.Footer className="flex flex-col gap-3 border-t border-border p-3 lg:flex-row lg:items-center lg:justify-between">
            <div className="text-sm text-muted">
              {start.toLocaleString()} to {end.toLocaleString()} of{' '}
              {rowCount.toLocaleString()} rows
            </div>
            <div className="flex flex-wrap items-center gap-3 lg:justify-end">
              <Select
                aria-label="Rows per page"
                className="w-24"
                selectedKey={String(pageSize)}
                onSelectionChange={(key: Key | null) => {
                  if (key == null) return
                  table.setPageSize(Number(key))
                  table.setPageIndex(0)
                }}
              >
                <Select.Trigger>
                  <Select.Value />
                  <Select.Indicator />
                </Select.Trigger>
                <Select.Popover>
                  <ListBox>
                    {pageSizeOptions.map((option) => (
                      <ListBoxItem key={option} id={option} textValue={option}>
                        {option}
                      </ListBoxItem>
                    ))}
                  </ListBox>
                </Select.Popover>
              </Select>
              <Pagination size="sm">
                <Pagination.Content>
                  <Pagination.Item>
                    <Pagination.Previous
                      isDisabled={!table.getCanPreviousPage()}
                      onPress={() => table.previousPage()}
                    >
                      <Pagination.PreviousIcon />
                      Prev
                    </Pagination.Previous>
                  </Pagination.Item>
                  {pageItems.map((page, index) =>
                    page === 'ellipsis' ? (
                      <Pagination.Item key={`ellipsis-${index}`}>
                        <Pagination.Ellipsis />
                      </Pagination.Item>
                    ) : (
                      <Pagination.Item key={page}>
                        <Pagination.Link
                          isActive={page === pageIndex + 1}
                          onPress={() => table.setPageIndex(page - 1)}
                        >
                          {page}
                        </Pagination.Link>
                      </Pagination.Item>
                    ),
                  )}
                  <Pagination.Item>
                    <Pagination.Next
                      isDisabled={!table.getCanNextPage()}
                      onPress={() => table.nextPage()}
                    >
                      Next
                      <Pagination.NextIcon />
                    </Pagination.Next>
                  </Pagination.Item>
                </Pagination.Content>
              </Pagination>
            </div>
          </Table.Footer>
        </Table>
      </div>
    </main>
  )
}

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

ReactDOM.createRoot(rootElement).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)
import * as React from 'react'
import ReactDOM from 'react-dom/client'
import {
  Button,
  Input,
  ListBox,
  ListBoxItem,
  Pagination,
  Select,
  Table,
  cn,
} from '@heroui/react'
import {
  createColumnHelper,
  createFilteredRowModel,
  createPaginatedRowModel,
  createSortedRowModel,
  filterFns,
  globalFilteringFeature,
  rowPaginationFeature,
  rowSortingFeature,
  sortFns,
  tableFeatures,
  useTable,
} from '@tanstack/react-table'
import { makeData } from './makeData'
import type { Key, SortDescriptor } from '@heroui/react'
import type { SortingState } from '@tanstack/react-table'
import type { Person } from './makeData'
import './index.css'

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

const columnHelper = createColumnHelper<typeof _features, Person>()
function toSortDescriptor(sorting: SortingState): SortDescriptor | undefined {
  const sort = sorting[0]
  if (!sort) return undefined
  return {
    column: sort.id,
    direction: sort.desc ? 'descending' : 'ascending',
  }
}

function toSortingState(descriptor: SortDescriptor): SortingState {
  return [
    {
      id: String(descriptor.column),
      desc: descriptor.direction === 'descending',
    },
  ]
}

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) => <span className="italic">{info.getValue<string>()}</span>,
  }),
  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',
  }),
])

const pageSizeOptions = ['10', '20', '30', '40', '50']

function getPageItems(pageIndex: number, pageCount: number) {
  const currentPage = pageIndex + 1
  const pages = new Set<number>([
    1,
    pageCount,
    currentPage - 1,
    currentPage,
    currentPage + 1,
  ])

  return Array.from(pages)
    .filter((page) => page >= 1 && page <= pageCount)
    .sort((a, b) => a - b)
    .reduce<Array<number | 'ellipsis'>>((items, page) => {
      const previous = items[items.length - 1]
      if (typeof previous === 'number' && page - previous > 1) {
        items.push('ellipsis')
      }
      items.push(page)
      return items
    }, [])
}

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
  )

  const pageIndex = table.state.pagination.pageIndex
  const pageSize = table.state.pagination.pageSize
  const pageCount = table.getPageCount()
  const pageItems = getPageItems(pageIndex, pageCount)
  const rowCount = table.getPrePaginatedRowModel().rows.length
  const start = rowCount === 0 ? 0 : pageIndex * pageSize + 1
  const end = Math.min((pageIndex + 1) * pageSize, rowCount)

  return (
    <main className="mx-auto max-w-6xl px-6 py-8">
      <div className="flex flex-col gap-5">
        <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
          <Input
            aria-label="Search all columns"
            className="w-full sm:w-[360px]"
            placeholder="Search all columns..."
            value={table.state.globalFilter ?? ''}
            onChange={(event) =>
              table.setGlobalFilter(event.currentTarget.value)
            }
          />
          <div className="flex flex-wrap gap-2">
            <Button variant="secondary" onPress={refreshData}>
              Regenerate Data
            </Button>
            <Button variant="secondary" onPress={stressTest}>
              Stress Test (10k rows)
            </Button>
          </div>
        </div>

        <Table className="overflow-hidden rounded-lg border border-border">
          <Table.ScrollContainer>
            <Table.Content
              aria-label="Hero UI TanStack Table example"
              className="min-w-[760px]"
              sortDescriptor={toSortDescriptor(table.state.sorting)}
              onSortChange={(descriptor) =>
                table.setSorting(toSortingState(descriptor))
              }
            >
              <Table.Header>
                {table.getHeaderGroups()[0]?.headers.map((header) => (
                  <Table.Column
                    key={header.id}
                    id={header.column.id}
                    allowsSorting={header.column.getCanSort()}
                    isRowHeader={header.id === 'firstName'}
                  >
                    {({ sortDirection }) => (
                      <span className="flex items-center justify-between gap-2 font-semibold">
                        <table.FlexRender header={header} />
                        <span
                          aria-hidden="true"
                          className={cn(
                            'text-xs text-muted transition-transform',
                            sortDirection === 'descending' && 'rotate-180',
                            !sortDirection && 'opacity-40',
                          )}
                        >
                          {sortDirection ? '' : ''}
                        </span>
                      </span>
                    )}
                  </Table.Column>
                ))}
              </Table.Header>
              <Table.Body
                renderEmptyState={() => (
                  <div className="py-10 text-center text-sm text-muted">
                    No results.
                  </div>
                )}
              >
                {table.getRowModel().rows.map((row) => (
                  <Table.Row key={row.id} id={row.id}>
                    {row.getAllCells().map((cell) => (
                      <Table.Cell key={cell.id}>
                        <table.FlexRender cell={cell} />
                      </Table.Cell>
                    ))}
                  </Table.Row>
                ))}
              </Table.Body>
            </Table.Content>
          </Table.ScrollContainer>

          <Table.Footer className="flex flex-col gap-3 border-t border-border p-3 lg:flex-row lg:items-center lg:justify-between">
            <div className="text-sm text-muted">
              {start.toLocaleString()} to {end.toLocaleString()} of{' '}
              {rowCount.toLocaleString()} rows
            </div>
            <div className="flex flex-wrap items-center gap-3 lg:justify-end">
              <Select
                aria-label="Rows per page"
                className="w-24"
                selectedKey={String(pageSize)}
                onSelectionChange={(key: Key | null) => {
                  if (key == null) return
                  table.setPageSize(Number(key))
                  table.setPageIndex(0)
                }}
              >
                <Select.Trigger>
                  <Select.Value />
                  <Select.Indicator />
                </Select.Trigger>
                <Select.Popover>
                  <ListBox>
                    {pageSizeOptions.map((option) => (
                      <ListBoxItem key={option} id={option} textValue={option}>
                        {option}
                      </ListBoxItem>
                    ))}
                  </ListBox>
                </Select.Popover>
              </Select>
              <Pagination size="sm">
                <Pagination.Content>
                  <Pagination.Item>
                    <Pagination.Previous
                      isDisabled={!table.getCanPreviousPage()}
                      onPress={() => table.previousPage()}
                    >
                      <Pagination.PreviousIcon />
                      Prev
                    </Pagination.Previous>
                  </Pagination.Item>
                  {pageItems.map((page, index) =>
                    page === 'ellipsis' ? (
                      <Pagination.Item key={`ellipsis-${index}`}>
                        <Pagination.Ellipsis />
                      </Pagination.Item>
                    ) : (
                      <Pagination.Item key={page}>
                        <Pagination.Link
                          isActive={page === pageIndex + 1}
                          onPress={() => table.setPageIndex(page - 1)}
                        >
                          {page}
                        </Pagination.Link>
                      </Pagination.Item>
                    ),
                  )}
                  <Pagination.Item>
                    <Pagination.Next
                      isDisabled={!table.getCanNextPage()}
                      onPress={() => table.nextPage()}
                    >
                      Next
                      <Pagination.NextIcon />
                    </Pagination.Next>
                  </Pagination.Item>
                </Pagination.Content>
              </Pagination>
            </div>
          </Table.Footer>
        </Table>
      </div>
    </main>
  )
}

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

ReactDOM.createRoot(rootElement).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)