Scroll Containers
The default table renders its own scrolling element. Three props change that — pick one when the table shares a viewport with surrounding page chrome, or when virtualization needs to be driven by a different scrolling element.
import * as React from 'react'
import { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'
import { localModel } from '@virtuoso.dev/data-table'
const rows = Array.from({ length: 80 }, (_, index) => ({
id: `SKU-${String(index + 1).padStart(3, '0')}`,
name: `Product ${index + 1}`,
category: ['Office', 'Peripherals', 'Audio'][index % 3]!,
}))
const model = localModel({ data: rows })
export default function App() {
const [scrollParent, setScrollParent] = React.useState<HTMLDivElement | null>(null)
return (
<div ref={setScrollParent} className="h-[360px] overflow-auto rounded-xl border">
<div className="sticky top-0 z-10 border-b bg-background px-3 py-2 text-sm font-medium">Inventory workspace</div>
<DataTable computeRowKey={({ data }) => data.id} customScrollParent={scrollParent} increaseViewportBy={360} model={model}>
<DataTableColumn field="name">
<DataTableColumnHeader>Product</DataTableColumnHeader>
<DataTableCell className="font-medium">{({ cellValue }) => String(cellValue)}</DataTableCell>
</DataTableColumn>
<DataTableColumn field="category">
<DataTableColumnHeader>Category</DataTableColumnHeader>
<DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>
</DataTableColumn>
</DataTable>
</div>
)
}Fill available panel height
Section titled “Fill available panel height”For app pages where the table should consume the remaining space below fixed page chrome, keep the default internal table scroller and give the table a measured flex slot. This is usually better than guessing a pixel height.
The important pieces are:
- a parent with a real height, such as a route shell, panel, or card body
min-h-0on each flex ancestor between that measured parent and the tableshrink-0on controls above the tableflex-1 min-h-0onDataTable, plusstyle={{ height: '100%' }}
export function OrdersPage() {
return (
<section className="flex h-full min-h-0 flex-col">
<PageHeader className="shrink-0" />
<div className="flex min-h-0 flex-1 flex-col gap-3">
<Toolbar className="shrink-0" />
<DataTable className="min-h-0 flex-1" computeRowKey={({ data }) => data.id} model={model} style={{ height: '100%' }}>
<DataTableColumn field="name">
<DataTableColumnHeader>Order</DataTableColumnHeader>
<DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>
</DataTableColumn>
</DataTable>
</div>
</section>
)
}Use this pattern only when the parent is height-constrained. If the table lives in normal document flow, keep a fixed table height (style={{ height: 360 }}) or intentionally switch to useWindowScroll / customScrollParent.
Window scrolling
Section titled “Window scrolling”useWindowScroll makes the document viewport drive the table’s scroll state:
<DataTable model={model} useWindowScroll>
{/* columns */}
</DataTable>The table no longer renders an internal scroller; its height comes from rendered content and the surrounding page layout. Sticky headers now interact with document-level sticky offsets, so test them against your actual header/nav stack.
Existing parent element
Section titled “Existing parent element”customScrollParent attaches the table to an existing scrolling element:
const [scrollParent, setScrollParent] = React.useState<HTMLDivElement | null>(null)
return (
<div ref={setScrollParent} className="h-[480px] overflow-auto">
<Toolbar />
<DataTable customScrollParent={scrollParent} model={model}>
{/* columns */}
</DataTable>
</div>
)Custom scroll element
Section titled “Custom scroll element”ScrollElement replaces the default scroller component while keeping the rest of the table contained:
const ScrollElement = React.forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement>>(function ScrollElement(props, ref) {
return <div ref={ref} className="custom-scrollbar overflow-auto" {...props} />
})
<DataTable ScrollElement={ScrollElement} model={model} />Pick one scroll mode per table. For customScrollParent, make sure that element has a real height and is the element that actually scrolls.