Skip to content

Introduction

@requence/table is a headless virtualized table for React. It gives you high-performance scrolling over large datasets, Suspense-compatible pagination, and column resize persistence — without prescribing any visual design.

The package ships three building blocks:

ExportPurpose
VirtualTableCompound component for virtualized, scrollable tables.
useTableCacheSuspense-compatible paginated data cache with real-time mutations.
useTableColumnWidthsPersists user-resized column widths to localStorage.
Terminal window
npm install @requence/table

@requence/table requires React 18 or 19 and react-dom as peer dependencies:

"peerDependencies": {
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
}

@requence/table uses tailwind-merge internally to compose Tailwind utility classes. Your project must have Tailwind CSS configured for the component’s built-in styles to take effect.

The component itself does not bundle a stylesheet — it relies on Tailwind’s utility classes being available in your build pipeline.

VirtualTable follows a compound component pattern. You compose the table declaratively using sub-components like VirtualTable.Header, VirtualTable.Column, and VirtualTable.Body.

You declare what the table should contain, and VirtualTable handles how it renders — you never fight the component for control over layout or behavior.

The two hooks — useTableCache and useTableColumnWidths — are fully optional and independent of VirtualTable. You can use them separately, together, or not at all. If your data is already loaded (e.g. from a GraphQL query or local state), you can pass it directly to VirtualTable without any hooks.

Column widths support fr units for proportional sizing, pixel values for fixed widths, and minmax() constraints — all columns stay perfectly aligned across header, body, and skeleton rows.

Only rows visible in the viewport (plus a configurable overscan buffer) are rendered to the DOM. The component uses absolute positioning with translateY for flicker-free scrolling at high frame rates.

import { VirtualTable, useTableCache } from '@requence/table'
function UserTable() {
const cache = useTableCache('users', {
pageSize: 50,
getItemId: (user) => user.id,
compare: (a, b) => a.name.localeCompare(b.name),
fetchItems: async (offset, limit) => {
const res = await fetch(`/api/users?offset=${offset}&limit=${limit}`)
const data = await res.json()
return { items: data.users, total: data.total }
},
})
return (
<VirtualTable
totalCount={cache.totalCount}
rowHeight={40}
onRangeChange={cache.handleRangeChange}
className="h-[600px]"
>
<VirtualTable.Header className="bg-zinc-900 text-sm text-zinc-400">
<VirtualTable.Column width="2fr">Name</VirtualTable.Column>
<VirtualTable.Column width="2fr">Email</VirtualTable.Column>
<VirtualTable.Column width="1fr">Role</VirtualTable.Column>
</VirtualTable.Header>
<VirtualTable.Body>
{(index) => {
const user = cache.getItem(index)
if (!user) return null
return (
<VirtualTable.Row>
<VirtualTable.Cell>{user.name}</VirtualTable.Cell>
<VirtualTable.Cell>{user.email}</VirtualTable.Cell>
<VirtualTable.Cell>{user.role}</VirtualTable.Cell>
</VirtualTable.Row>
)
}}
</VirtualTable.Body>
</VirtualTable>
)
}

This renders a virtualized, paginated table that fetches data on scroll.

If your data is already loaded, you can use VirtualTable without useTableCache:

import { VirtualTable } from '@requence/table'
interface Group {
id: string
name: string
description: string
}
function GroupList({ groups }: { groups: Group[] }) {
return (
<VirtualTable totalCount={groups.length} rowHeight={40}>
<VirtualTable.Header>
<VirtualTable.Column width={250}>Name</VirtualTable.Column>
<VirtualTable.Column width="1fr">Description</VirtualTable.Column>
</VirtualTable.Header>
<VirtualTable.Body>
{(index) => {
const group = groups[index]
if (!group) return null
return (
<VirtualTable.Row>
<VirtualTable.Cell>{group.name}</VirtualTable.Cell>
<VirtualTable.Cell>{group.description}</VirtualTable.Cell>
</VirtualTable.Row>
)
}}
</VirtualTable.Body>
<VirtualTable.Empty>No groups found.</VirtualTable.Empty>
</VirtualTable>
)
}

The next pages dive into each concept in detail.