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.
Main exports
Section titled “Main exports”The package ships three building blocks:
| Export | Purpose |
|---|---|
VirtualTable | Compound component for virtualized, scrollable tables. |
useTableCache | Suspense-compatible paginated data cache with real-time mutations. |
useTableColumnWidths | Persists user-resized column widths to localStorage. |
Installation
Section titled “Installation”npm install @requence/tablebun add @requence/tablePeer dependencies
Section titled “Peer dependencies”@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"}Prerequisites
Section titled “Prerequisites”Tailwind CSS
Section titled “Tailwind CSS”@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.
Architecture
Section titled “Architecture”Headless compound component
Section titled “Headless compound component”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.
Hooks for state
Section titled “Hooks for state”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 layout
Section titled “Column layout”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.
Virtual scrolling
Section titled “Virtual scrolling”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.
Quick example
Section titled “Quick example”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.
Static table
Section titled “Static table”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.