Row DnD Table
Drag-and-drop row reordering with composable primitives.
Overview
Section titled “Overview”Preview with Controlled State
View Full State
[
{
"id": "TASK-001",
"title": "Set up project repository"
},
{
"id": "TASK-002",
"title": "Design database schema"
},
{
"id": "TASK-003",
"title": "Implement authentication"
},
{
"id": "TASK-004",
"title": "Create API endpoints"
},
{
"id": "TASK-005",
"title": "Write unit tests"
},
{
"id": "TASK-006",
"title": "Set up CI/CD pipeline"
},
{
"id": "TASK-007",
"title": "Deploy to staging"
},
{
"id": "TASK-008",
"title": "Performance optimization"
}
]Virtualized Row DnD
Section titled “Virtualized Row DnD”For large datasets, use DataTableVirtualizedDndBody which combines row virtualization with drag-and-drop. Only visible rows are rendered in the DOM for optimal performance.
Introduction
Section titled “Introduction”Row DnD lets users reorder table rows by dragging. Built with @dnd-kit and composable primitives that follow the shadcn/ui open-code pattern — copy the code, modify freely.
Installation
Section titled “Installation”Install the DataTable core and row DnD add-on:
First time using
@niko-table? See the Installation Guide to set up the registry.
For other add-ons or manual copy-paste, see the Installation Guide.
Prerequisites
Section titled “Prerequisites”We’ll build a task board with draggable rows:
type Task = { id: string title: string status: "todo" | "in-progress" | "done" | "cancelled" priority: "low" | "medium" | "high"}Basic Row DnD
Section titled “Basic Row DnD”Three components work together:
DataTableRowDndProvider— Wraps the table with DnD context and sensorsDataTableDndBody— Renders rows as draggable itemsDataTableRowDragHandle— A grip icon button for dragging
const [data, setData] = React.useState(initialData)
const columns: DataTableColumnDef<Task>[] = [ { id: "drag-handle", size: 40, header: () => null, cell: ({ row }) => <DataTableRowDragHandle rowId={row.id} />, enableSorting: false, enableHiding: false, }, // ... other columns]
return ( <DataTableRoot data={data} columns={columns} getRowId={(row) => row.id}> <DataTableRowDndProvider data={data} onReorder={setData}> <DataTable> <DataTableHeader /> <DataTableDndBody /> </DataTable> </DataTableRowDndProvider> </DataTableRoot>)Key Points
Section titled “Key Points”getRowIdis required — use stable, unique IDs (e.g., database IDs), not array indexes. Array indexes break DnD after reordering because the index no longer matches the original itemDataTableRowDndProvidermust wrap outside<DataTable>— DnD context creates<div>elements that can’t be inside<table>onReorderreceives the new data array afterarrayMove— just passsetData
Drag Handle Column
Section titled “Drag Handle Column”The drag handle is a dedicated column with DataTableRowDragHandle:
{ id: "drag-handle", size: 40, header: () => null, cell: ({ row }) => <DataTableRowDragHandle rowId={row.id} />, enableSorting: false, enableHiding: false,}Controlled State
Section titled “Controlled State”Track the data order externally:
const [data, setData] = React.useState(initialData)
// Reset to original orderconst resetData = () => setData(initialData)
return ( <DataTableRoot data={data} columns={columns} getRowId={(row) => row.id}> <DataTableRowDndProvider data={data} onReorder={setData}> <DataTable> <DataTableHeader /> <DataTableDndBody /> </DataTable> </DataTableRowDndProvider> </DataTableRoot>)Best Practices
Section titled “Best Practices”Don’t combine sorting or filtering with row DnD. Sorting and filtering override the manual row order — if a user drags row 3 to position 1, then a sort or filter resets it, the reorder is lost.
- Avoid
DataTableColumnSortMenu,DataTableSearchFilter, andDataTableFacetedFilterin DnD tables - If you need search, consider filtering the source data before passing it to the table
- Column DnD is safe to combine with sorting/filtering since column order is independent of data order
When to Use
Section titled “When to Use”✅ Use Row DnD when:
- Users need to manually prioritize or reorder items
- Building kanban boards, task lists, or playlist managers
- Order matters and should be persisted
❌ Consider other options when:
- Data has a natural sort order (use sorting instead)
- The table is read-only and order doesn’t matter
Next Steps
Section titled “Next Steps”- Column DnD Table — Drag-and-drop column reordering
- Row Selection Table — Combine with row selection