Data Table

Data Display

Stablev1.0.0

Sortable, filterable data table with infinite scroll and TanStack Query integration, built on Ariakit for accessibility.

Preview

Products
NamePrice
Product A$10Product B$20

Installation

pnpm add @component-labs/ui

Usage

import { DataTable } from "@component-labs/ui";

<DataTable
  data={items}
  label="Products"
  tableHeader={<tr><th>Name</th><th>Price</th></tr>}
  tableRow={(item) => <tr><td>{item.name}</td><td>{item.price}</td></tr>}
/>

Props

NameTypeDefaultDescription
data*T[]-Array of data items to display in the table
label*string-Accessible label for the table
tableHeader*ReactElement-Table header component
tableRow*(item: T, index: number) => ReactElement-Function to render each table row
descriptionstring-Optional description for assistive technology
loadingRowReactElement-Skeleton row component shown during loading
emptyRowReactElement-Component shown when no data is available
tableFooterReactElement-Optional table footer component
pageLimitnumber10Number of skeleton rows to show during loading
isLoadingbooleanfalseInitial loading state
hasNextPagebooleanfalseWhether there are more pages to fetch
fetchNextPage() => void-Callback to fetch the next page
isFetchingNextPagebooleanfalseWhether currently fetching next page
isErrorbooleanfalseError state - prevents infinite retry loops
triggerOffsetnumber5Number of rows from end to trigger fetchNextPage
rootMarginstring'200px'IntersectionObserver rootMargin

Examples

Basic Table

Simple data table with static data

const products = [
  { id: 1, name: "Product A", price: "$10" },
  { id: 2, name: "Product B", price: "$20" },
];

<DataTable
  data={products}
  label="Products"
  tableHeader={
    <tr>
      <th>Name</th>
      <th>Price</th>
    </tr>
  }
  tableRow={(product) => (
    <tr key={product.id}>
      <td>{product.name}</td>
      <td>{product.price}</td>
    </tr>
  )}
/>

With Infinite Scroll

Infinite scroll with TanStack Query

const { data, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage } =
  useInfiniteQuery({ ... });

<DataTable
  data={data?.pages.flatMap(p => p.items) ?? []}
  label="Items"
  isLoading={isLoading}
  hasNextPage={hasNextPage}
  fetchNextPage={fetchNextPage}
  isFetchingNextPage={isFetchingNextPage}
  tableHeader={<tr><th>Name</th></tr>}
  tableRow={(item) => <tr><td>{item.name}</td></tr>}
  loadingRow={<tr><td><Skeleton /></td></tr>}
/>

With Empty State

Custom empty state when no data

<DataTable
  data={[]}
  label="Users"
  tableHeader={<tr><th>Name</th><th>Email</th></tr>}
  tableRow={(user) => <tr><td>{user.name}</td><td>{user.email}</td></tr>}
  emptyRow={
    <div className="text-center py-8">
      <p>No users found</p>
      <Button onClick={addUser}>Add User</Button>
    </div>
  }
/>

Performance

Bundle Size

~6kB gzipped

Minified and gzipped

Dependencies

  • @ariakit/react

Accessibility

  • Built on Ariakit's Composite component for keyboard navigation
  • Proper ARIA table roles (table, row, cell, rowgroup)
  • Screen reader announcements for loading states
  • Accessible labels and descriptions
  • Keyboard navigation between cells
  • Live region announcements for data changes