Defining Columns
DataTableColumn declares a column. Its field prop does double duty: it’s the row-data lookup key and the public column identifier used by persistence, reordering, resizing, and visibility.
<DataTableColumn field="name">
<DataTableColumnHeader>Name</DataTableColumnHeader>
<DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>
</DataTableColumn>For a row { name: 'Standing Desk' }, the cell renderer receives 'Standing Desk' as cellValue. Omitting DataTableCell falls back to the field value; omitting DataTableColumnHeader falls back to the field name.
Minimal columns
Section titled “Minimal columns”Add an explicit header or cell when you need formatting; leave both off when the field name and stringified value are enough.
import { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'
import { localModel } from '@virtuoso.dev/data-table'
const rows = [
{ id: 'SKU-001', name: 'Standing Desk', category: 'Office', stock: 14 },
{ id: 'SKU-002', name: 'USB-C Dock', category: 'Peripherals', stock: 42 },
{ id: 'SKU-003', name: 'Mechanical Keyboard', category: 'Peripherals', stock: 28 },
]
const model = localModel({ data: rows })
export default function App() {
return (
<DataTable className="rounded-xl" model={model} style={{ height: 260 }}>
<DataTableColumn field="name">
<DataTableColumnHeader>Product</DataTableColumnHeader>
<DataTableCell className="font-medium">{({ cellValue }) => String(cellValue)}</DataTableCell>
</DataTableColumn>
<DataTableColumn field="category" />
<DataTableColumn field="stock">
<DataTableColumnHeader className="justify-end">Stock</DataTableColumnHeader>
<DataTableCell className="text-right tabular-nums">{({ cellValue }) => String(cellValue)}</DataTableCell>
</DataTableColumn>
</DataTable>
)
}Declaration order
Section titled “Declaration order”Columns render in declaration order. Reordering and persistence change the runtime order; the declaration order stays as the baseline used after a reset.
<DataTable model={model}>
<DataTableColumn field="name" />
<DataTableColumn field="category" />
<DataTableColumn field="price" />
</DataTable>Visibility and sticky
Section titled “Visibility and sticky”visible={false} hides a column by default. Runtime visibility lives in the Column Visibility page.
<DataTableColumn field="internalSku" visible={false} />sticky="left" or sticky="right" pins a column for row identity, IDs, or actions that should stay visible during horizontal scrolling — see Sticky Columns.
<DataTableColumn field="name" sticky="left" />
<DataTableColumn field="actions" sticky="right" />Generated columns
Section titled “Generated columns”For dynamic field lists, map them to DataTableColumn declarations. Keep the field order deterministic — the field values become the column keys.
const fields = ['name', 'category', 'stock']
;<DataTable model={model}>
{fields.map((field) => (
<DataTableColumn field={field} key={field}>
<DataTableColumnHeader>{field}</DataTableColumnHeader>
<DataTableCell>{({ cellValue }) => String(cellValue ?? '')}</DataTableCell>
</DataTableColumn>
))}
</DataTable>When the field list itself is discovered from the first data result, see Runtime Columns.