Inside the shadcn Wrapper
This page applies to the shadcn install path. If you installed @virtuoso.dev/data-table directly without the registry, you own the equivalent components in your own files — the wrapper-vs-headless split still applies, but the file locations are yours to decide. See Headless Install for that route.
Running npx shadcn add copied a folder of components into @/components/ui/data-table. That folder is your code — it ships in your repo, it’s checked into your git history, and editing it doesn’t fork the table’s behavior. The behavior lives somewhere else.
import { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'
import { localModel } from '@virtuoso.dev/data-table'
const rows = [
{ product: 'Standing Desk', owner: 'Avery', status: 'Launch ready' },
{ product: 'USB-C Dock', owner: 'Morgan', status: 'In review' },
{ product: 'Mechanical Keyboard', owner: 'Riley', status: 'Backorder' },
]
const model = localModel({ data: rows })
export default function App() {
return (
<DataTable className="rounded-xl border-2 border-border/70 shadow-sm" model={model} style={{ height: 280 }}>
<DataTableColumn field="product">
<DataTableColumnHeader>Product</DataTableColumnHeader>
<DataTableCell className="font-medium">{({ cellValue }) => String(cellValue)}</DataTableCell>
</DataTableColumn>
<DataTableColumn field="owner">
<DataTableColumnHeader>Owner</DataTableColumnHeader>
<DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>
</DataTableColumn>
<DataTableColumn field="status">
<DataTableColumnHeader>Status</DataTableColumnHeader>
<DataTableCell>
{({ cellValue }) => <span className="rounded-full bg-muted px-2 py-1 text-xs font-medium">{String(cellValue)}</span>}
</DataTableCell>
</DataTableColumn>
</DataTable>
)
}What the wrapper file owns
Section titled “What the wrapper file owns”The wrapper covers presentation:
- default row, header, sticky-column, empty, and loading components
- Tailwind classes for cells, headers, and group headers
- ergonomic names (
DataTable,DataTableColumn,DataTableColumnHeader,DataTableCell) - re-exports of the headless hooks and helpers you’ll use day-to-day
Edit it like any other component in your app. Tailwind classes, default props, alternate variants, additional re-exports — all fair game. The wrapper file imports behavior from @virtuoso.dev/data-table, so your styling edits never fork table mechanics.
What the headless package owns
Section titled “What the headless package owns”@virtuoso.dev/data-table covers behavior:
- virtualization, measurement, scrolling
localModel()andremoteModel()- the reactive engine (
engineRef,useRemoteCellValue,useRemotePublisher) - opt-in feature modules (resize, reorder, visibility, persistence)
Behavior imports always come from @virtuoso.dev/data-table. If you find yourself reaching into the headless package to change behavior, that’s a signal you want an issue or a PR rather than a fork.
Feature-specific wrappers
Section titled “Feature-specific wrappers”Some opt-in features ship their own wrapper components, following the same presentation/behavior split:
data-table-resize-handle— resize handle UIdata-table-reorder-grip,data-table-reorder-drop-zone,data-table-draggable-group-header— reorder UI
Each one is header-slot UI you can restyle. The streams they publish to live in the corresponding @virtuoso.dev/data-table/... subpaths — see Header Slots for how those slots compose, and the column feature pages for what each registry component does.
When you’ll come back here
Section titled “When you’ll come back here”The wrapper is the right place to change:
- default Tailwind classes on
DataTable,DataTableCell,DataTableColumnHeader - the default
Row,EmptyPlaceholder,LoadingPlaceholderyour team should see - ergonomic re-exports you add for your codebase
For replacing the table’s structural components (Row, GroupHeader, sticky wrappers) on a per-instance basis without touching the wrapper file, see Replacing Internals.