Row DnD Table
Drag-and-drop row reordering with composable primitives.
Reorder rows via drag-and-drop using DataTableRowDndProvider and a dedicated drag-handle column. Important constraint: do not combine row DnD with sorting or filtering — the user’s manual order will fight whatever the table is computing, so disable those add-ons on a DnD table. Column resize is fine — drop in <DataTableColumnResize /> (same as other tables); keep enableResizing: false on the drag-handle / # columns.
Overview
Section titled “Overview”Preview with Controlled State
Virtualized Row DnD
Section titled “Virtualized Row DnD”For large datasets, use DataTableVirtualizedFlexHeader + DataTableVirtualizedDndBody which combine row virtualization with drag-and-drop. Only visible rows are rendered in the DOM for optimal performance. The same <DataTableColumnResize /> marker works here — grips appear on the flex header.
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
import { DataTableDndBody } from "@/components/niko-table/core/data-table-row-dnd-structure"
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, enableResizing: false, }, // ... other columns]
return ( <DataTableRoot data={data} columns={columns} getRowId={(row) => row.id}> <DataTableColumnResize /> <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:
import { DataTableDndBody } from "@/components/niko-table/core/data-table-row-dnd-structure"
const [data, setData] = React.useState(initialData)
// Reset to original orderconst resetData = () => setData(initialData)
return ( <DataTableRoot data={data} columns={columns} getRowId={(row) => row.id}> <DataTableColumnResize /> <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