File: row-dnd.md | Updated: 11/15/2025
Search...
+ K
Auto
Docs Examples GitHub Contributors
Docs Examples GitHub Contributors
Docs Examples GitHub Contributors
Docs Examples Github Contributors
Docs Examples Github Contributors
Docs Examples Github Contributors
Docs Examples Github Contributors
Docs Examples Github Contributors
Maintainers Partners Support Learn StatsBETA Discord Merch Blog GitHub Ethos Brand Guide
Documentation
Framework
React
Version
v8
Search...
+ K
Menu
Getting Started
Core Guides
Feature Guides
Core APIs
Feature APIs
Enterprise
Examples
Framework
React
Version
v8
Menu
Getting Started
Core Guides
Feature Guides
Core APIs
Feature APIs
Enterprise
Examples
React Example: Row Dnd
===================================================================================================================================================================================================================================================================================================================================================================================================
Code ExplorerCode
Interactive SandboxSandbox
src
index.css
main.tsx
makeData.ts
.gitignore
README.md
index.html
package.json
tsconfig.json
vite.config.js
tsx
import React, { CSSProperties } from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import {
ColumnDef,
Row,
flexRender,
getCoreRowModel,
useReactTable,
} from '@tanstack/react-table'
import { makeData, Person } from './makeData'
// needed for table body level scope DnD setup
import {
DndContext,
KeyboardSensor,
MouseSensor,
TouchSensor,
closestCenter,
type DragEndEvent,
type UniqueIdentifier,
useSensor,
useSensors,
} from '@dnd-kit/core'
import { restrictToVerticalAxis } from '@dnd-kit/modifiers'
import {
arrayMove,
SortableContext,
verticalListSortingStrategy,
} from '@dnd-kit/sortable'
// needed for row & cell level scope DnD setup
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
// Cell Component
const RowDragHandleCell = ({ rowId }: { rowId: string }) => {
const { attributes, listeners } = useSortable({
id: rowId,
})
return (
// Alternatively, you could set these attributes on the rows themselves
<button {...attributes} {...listeners}>
🟰
</button>
)
}
// Row Component
const DraggableRow = ({ row }: { row: Row<Person> }) => {
const { transform, transition, setNodeRef, isDragging } = useSortable({
id: row.original.userId,
})
const style: CSSProperties = {
transform: CSS.Transform.toString(transform), //let dnd-kit do its thing
transition: transition,
opacity: isDragging ? 0.8 : 1,
zIndex: isDragging ? 1 : 0,
position: 'relative',
}
return (
// connect row ref to dnd-kit, apply important styles
<tr ref={setNodeRef} style={style}>
{row.getVisibleCells().map(cell => (
<td key={cell.id} style={{ width: cell.column.getSize() }}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
)
}
// Table Component
function App() {
const columns = React.useMemo<ColumnDef<Person>[]>(
() => [\
// Create a dedicated drag handle column. Alternatively, you could just set up dnd events on the rows themselves.\
{\
id: 'drag-handle',\
header: 'Move',\
cell: ({ row }) => <RowDragHandleCell rowId={row.id} />,\
size: 60,\
},\
{\
accessorKey: 'firstName',\
cell: info => info.getValue(),\
},\
{\
accessorFn: row => row.lastName,\
id: 'lastName',\
cell: info => info.getValue(),\
header: () => <span>Last Name</span>,\
},\
{\
accessorKey: 'age',\
header: () => 'Age',\
},\
{\
accessorKey: 'visits',\
header: () => <span>Visits</span>,\
},\
{\
accessorKey: 'status',\
header: 'Status',\
},\
{\
accessorKey: 'progress',\
header: 'Profile Progress',\
},\
],
[]
)
const [data, setData] = React.useState(() => makeData(20))
const dataIds = React.useMemo<UniqueIdentifier[]>(
() => data?.map(({ userId }) => userId),
[data]
)
const rerender = () => setData(() => makeData(20))
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getRowId: row => row.userId, //required because row indexes will change
debugTable: true,
debugHeaders: true,
debugColumns: true,
})
// reorder rows after drag & drop
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event
if (active && over && active.id !== over.id) {
setData(data => {
const oldIndex = dataIds.indexOf(active.id)
const newIndex = dataIds.indexOf(over.id)
return arrayMove(data, oldIndex, newIndex) //this is just a splice util
})
}
}
const sensors = useSensors(
useSensor(MouseSensor, {}),
useSensor(TouchSensor, {}),
useSensor(KeyboardSensor, {})
)
return (
// NOTE: This provider creates div elements, so don't nest inside of <table> elements
<DndContext
collisionDetection={closestCenter}
modifiers={[restrictToVerticalAxis]}
onDragEnd={handleDragEnd}
sensors={sensors}
>
<div className="p-2">
<div className="h-4" />
<div className="flex flex-wrap gap-2">
<button onClick={() => rerender()} className="border p-1">
Regenerate
</button>
</div>
<div className="h-4" />
<table>
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => (
<th key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</th>
))}
</tr>
))}
</thead>
<tbody>
<SortableContext
items={dataIds}
strategy={verticalListSortingStrategy}
>
{table.getRowModel().rows.map(row => (
<DraggableRow key={row.id} row={row} />
))}
</SortableContext>
</tbody>
</table>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
</DndContext>
)
}
const rootElement = document.getElementById('root')
if (!rootElement) throw new Error('Failed to find the root element')
ReactDOM.createRoot(rootElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
import React, { CSSProperties } from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import {
ColumnDef,
Row,
flexRender,
getCoreRowModel,
useReactTable,
} from '@tanstack/react-table'
import { makeData, Person } from './makeData'
// needed for table body level scope DnD setup
import {
DndContext,
KeyboardSensor,
MouseSensor,
TouchSensor,
closestCenter,
type DragEndEvent,
type UniqueIdentifier,
useSensor,
useSensors,
} from '@dnd-kit/core'
import { restrictToVerticalAxis } from '@dnd-kit/modifiers'
import {
arrayMove,
SortableContext,
verticalListSortingStrategy,
} from '@dnd-kit/sortable'
// needed for row & cell level scope DnD setup
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
// Cell Component
const RowDragHandleCell = ({ rowId }: { rowId: string }) => {
const { attributes, listeners } = useSortable({
id: rowId,
})
return (
// Alternatively, you could set these attributes on the rows themselves
<button {...attributes} {...listeners}>
🟰
</button>
)
}
// Row Component
const DraggableRow = ({ row }: { row: Row<Person> }) => {
const { transform, transition, setNodeRef, isDragging } = useSortable({
id: row.original.userId,
})
const style: CSSProperties = {
transform: CSS.Transform.toString(transform), //let dnd-kit do its thing
transition: transition,
opacity: isDragging ? 0.8 : 1,
zIndex: isDragging ? 1 : 0,
position: 'relative',
}
return (
// connect row ref to dnd-kit, apply important styles
<tr ref={setNodeRef} style={style}>
{row.getVisibleCells().map(cell => (
<td key={cell.id} style={{ width: cell.column.getSize() }}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
)
}
// Table Component
function App() {
const columns = React.useMemo<ColumnDef<Person>[]>(
() => [\
// Create a dedicated drag handle column. Alternatively, you could just set up dnd events on the rows themselves.\
{\
id: 'drag-handle',\
header: 'Move',\
cell: ({ row }) => <RowDragHandleCell rowId={row.id} />,\
size: 60,\
},\
{\
accessorKey: 'firstName',\
cell: info => info.getValue(),\
},\
{\
accessorFn: row => row.lastName,\
id: 'lastName',\
cell: info => info.getValue(),\
header: () => <span>Last Name</span>,\
},\
{\
accessorKey: 'age',\
header: () => 'Age',\
},\
{\
accessorKey: 'visits',\
header: () => <span>Visits</span>,\
},\
{\
accessorKey: 'status',\
header: 'Status',\
},\
{\
accessorKey: 'progress',\
header: 'Profile Progress',\
},\
],
[]
)
const [data, setData] = React.useState(() => makeData(20))
const dataIds = React.useMemo<UniqueIdentifier[]>(
() => data?.map(({ userId }) => userId),
[data]
)
const rerender = () => setData(() => makeData(20))
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getRowId: row => row.userId, //required because row indexes will change
debugTable: true,
debugHeaders: true,
debugColumns: true,
})
// reorder rows after drag & drop
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event
if (active && over && active.id !== over.id) {
setData(data => {
const oldIndex = dataIds.indexOf(active.id)
const newIndex = dataIds.indexOf(over.id)
return arrayMove(data, oldIndex, newIndex) //this is just a splice util
})
}
}
const sensors = useSensors(
useSensor(MouseSensor, {}),
useSensor(TouchSensor, {}),
useSensor(KeyboardSensor, {})
)
return (
// NOTE: This provider creates div elements, so don't nest inside of <table> elements
<DndContext
collisionDetection={closestCenter}
modifiers={[restrictToVerticalAxis]}
onDragEnd={handleDragEnd}
sensors={sensors}
>
<div className="p-2">
<div className="h-4" />
<div className="flex flex-wrap gap-2">
<button onClick={() => rerender()} className="border p-1">
Regenerate
</button>
</div>
<div className="h-4" />
<table>
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => (
<th key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</th>
))}
</tr>
))}
</thead>
<tbody>
<SortableContext
items={dataIds}
strategy={verticalListSortingStrategy}
>
{table.getRowModel().rows.map(row => (
<DraggableRow key={row.id} row={row} />
))}
</SortableContext>
</tbody>
</table>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
</DndContext>
)
}
const rootElement = document.getElementById('root')
if (!rootElement) throw new Error('Failed to find the root element')
ReactDOM.createRoot(rootElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
