Embed a Table in a Shared Scroll Area
The table renders inside a parent with its own overflow: auto, so notes, filters, and the table body all scroll together. customScrollParent tells the table to drive virtualization from that outer element instead of its own scroller.
APIs used
Section titled “APIs used”customScrollParent— attaches the table to an external scrolling element- An outer container with
overflow: autoand a real height
import { useState } from 'react'
import { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'
import { products } from './data'
import { localModel } from '@virtuoso.dev/data-table'
const model = localModel({ data: products })
export default function App() {
const [scrollParent, setScrollParent] = useState<HTMLDivElement | null>(null)
return (
<div className="rounded-xl">
<div className="border-b px-4 py-3">
<p className="font-semibold">Shared scroll surface</p>
<p className="text-sm text-muted-foreground">The note above the table scrolls together with the rows.</p>
</div>
<div className="h-[420px] overflow-auto" ref={setScrollParent}>
<div className="border-b px-4 py-3 text-sm text-muted-foreground">
Filters, charts, or help text can live in the same scrolling context as the table body.
</div>
<DataTable className="min-w-[720px]" customScrollParent={scrollParent} model={model}>
<DataTableColumn field="name">
<DataTableColumnHeader>Product</DataTableColumnHeader>
<DataTableCell className="font-medium">{({ row }) => row.data.name}</DataTableCell>
</DataTableColumn>
<DataTableColumn field="category">
<DataTableColumnHeader>Category</DataTableColumnHeader>
<DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>
</DataTableColumn>
<DataTableColumn field="region">
<DataTableColumnHeader>Region</DataTableColumnHeader>
<DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>
</DataTableColumn>
<DataTableColumn field="status">
<DataTableColumnHeader>Status</DataTableColumnHeader>
<DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>
</DataTableColumn>
</DataTable>
</div>
</div>
)
}The outer container’s height bounds the entire layout, including the table. Sticky table headers now interact with sibling sticky elements inside the same scroll context — test both together.
Do not set style={{ height }} on the table when customScrollParent is in use. The table grows to its virtualized content height; the outer container’s scroll height drives virtualization. A fixed table height clips the rows before the scroll parent can reach them, and the bottom rows appear to never load.