File: column-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: Column 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 {
Cell,
ColumnDef,
Header,
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,
useSensor,
useSensors,
} from '@dnd-kit/core'
import { restrictToHorizontalAxis } from '@dnd-kit/modifiers'
import {
arrayMove,
SortableContext,
horizontalListSortingStrategy,
} from '@dnd-kit/sortable'
// needed for row & cell level scope DnD setup
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
const DraggableTableHeader = ({
header,
}: {
header: Header<Person, unknown>
}) => {
const { attributes, isDragging, listeners, setNodeRef, transform } =
useSortable({
id: header.column.id,
})
const style: CSSProperties = {
opacity: isDragging ? 0.8 : 1,
position: 'relative',
transform: CSS.Translate.toString(transform), // translate instead of transform to avoid squishing
transition: 'width transform 0.2s ease-in-out',
whiteSpace: 'nowrap',
width: header.column.getSize(),
zIndex: isDragging ? 1 : 0,
}
return (
<th colSpan={header.colSpan} ref={setNodeRef} style={style}>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
<button {...attributes} {...listeners}>
🟰
</button>
</th>
)
}
const DragAlongCell = ({ cell }: { cell: Cell<Person, unknown> }) => {
const { isDragging, setNodeRef, transform } = useSortable({
id: cell.column.id,
})
const style: CSSProperties = {
opacity: isDragging ? 0.8 : 1,
position: 'relative',
transform: CSS.Translate.toString(transform), // translate instead of transform to avoid squishing
transition: 'width transform 0.2s ease-in-out',
width: cell.column.getSize(),
zIndex: isDragging ? 1 : 0,
}
return (
<td style={style} ref={setNodeRef}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
)
}
function App() {
const columns = React.useMemo<ColumnDef<Person>[]>(
() => [\
{\
accessorKey: 'firstName',\
cell: info => info.getValue(),\
id: 'firstName',\
size: 150,\
},\
{\
accessorFn: row => row.lastName,\
cell: info => info.getValue(),\
header: () => <span>Last Name</span>,\
id: 'lastName',\
size: 150,\
},\
{\
accessorKey: 'age',\
header: () => 'Age',\
id: 'age',\
size: 120,\
},\
{\
accessorKey: 'visits',\
header: () => <span>Visits</span>,\
id: 'visits',\
size: 120,\
},\
{\
accessorKey: 'status',\
header: 'Status',\
id: 'status',\
size: 150,\
},\
{\
accessorKey: 'progress',\
header: 'Profile Progress',\
id: 'progress',\
size: 180,\
},\
],
[]
)
const [data, setData] = React.useState(() => makeData(20))
const [columnOrder, setColumnOrder] = React.useState<string[]>(() =>
columns.map(c => c.id!)
)
const rerender = () => setData(() => makeData(20))
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
state: {
columnOrder,
},
onColumnOrderChange: setColumnOrder,
debugTable: true,
debugHeaders: true,
debugColumns: true,
})
// reorder columns after drag & drop
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event
if (active && over && active.id !== over.id) {
setColumnOrder(columnOrder => {
const oldIndex = columnOrder.indexOf(active.id as string)
const newIndex = columnOrder.indexOf(over.id as string)
return arrayMove(columnOrder, 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={[restrictToHorizontalAxis]}
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}>
<SortableContext
items={columnOrder}
strategy={horizontalListSortingStrategy}
>
{headerGroup.headers.map(header => (
<DraggableTableHeader key={header.id} header={header} />
))}
</SortableContext>
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map(row => (
<tr key={row.id}>
{row.getVisibleCells().map(cell => (
<SortableContext
key={cell.id}
items={columnOrder}
strategy={horizontalListSortingStrategy}
>
<DragAlongCell key={cell.id} cell={cell} />
</SortableContext>
))}
</tr>
))}
</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 {
Cell,
ColumnDef,
Header,
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,
useSensor,
useSensors,
} from '@dnd-kit/core'
import { restrictToHorizontalAxis } from '@dnd-kit/modifiers'
import {
arrayMove,
SortableContext,
horizontalListSortingStrategy,
} from '@dnd-kit/sortable'
// needed for row & cell level scope DnD setup
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
const DraggableTableHeader = ({
header,
}: {
header: Header<Person, unknown>
}) => {
const { attributes, isDragging, listeners, setNodeRef, transform } =
useSortable({
id: header.column.id,
})
const style: CSSProperties = {
opacity: isDragging ? 0.8 : 1,
position: 'relative',
transform: CSS.Translate.toString(transform), // translate instead of transform to avoid squishing
transition: 'width transform 0.2s ease-in-out',
whiteSpace: 'nowrap',
width: header.column.getSize(),
zIndex: isDragging ? 1 : 0,
}
return (
<th colSpan={header.colSpan} ref={setNodeRef} style={style}>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
<button {...attributes} {...listeners}>
🟰
</button>
</th>
)
}
const DragAlongCell = ({ cell }: { cell: Cell<Person, unknown> }) => {
const { isDragging, setNodeRef, transform } = useSortable({
id: cell.column.id,
})
const style: CSSProperties = {
opacity: isDragging ? 0.8 : 1,
position: 'relative',
transform: CSS.Translate.toString(transform), // translate instead of transform to avoid squishing
transition: 'width transform 0.2s ease-in-out',
width: cell.column.getSize(),
zIndex: isDragging ? 1 : 0,
}
return (
<td style={style} ref={setNodeRef}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
)
}
function App() {
const columns = React.useMemo<ColumnDef<Person>[]>(
() => [\
{\
accessorKey: 'firstName',\
cell: info => info.getValue(),\
id: 'firstName',\
size: 150,\
},\
{\
accessorFn: row => row.lastName,\
cell: info => info.getValue(),\
header: () => <span>Last Name</span>,\
id: 'lastName',\
size: 150,\
},\
{\
accessorKey: 'age',\
header: () => 'Age',\
id: 'age',\
size: 120,\
},\
{\
accessorKey: 'visits',\
header: () => <span>Visits</span>,\
id: 'visits',\
size: 120,\
},\
{\
accessorKey: 'status',\
header: 'Status',\
id: 'status',\
size: 150,\
},\
{\
accessorKey: 'progress',\
header: 'Profile Progress',\
id: 'progress',\
size: 180,\
},\
],
[]
)
const [data, setData] = React.useState(() => makeData(20))
const [columnOrder, setColumnOrder] = React.useState<string[]>(() =>
columns.map(c => c.id!)
)
const rerender = () => setData(() => makeData(20))
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
state: {
columnOrder,
},
onColumnOrderChange: setColumnOrder,
debugTable: true,
debugHeaders: true,
debugColumns: true,
})
// reorder columns after drag & drop
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event
if (active && over && active.id !== over.id) {
setColumnOrder(columnOrder => {
const oldIndex = columnOrder.indexOf(active.id as string)
const newIndex = columnOrder.indexOf(over.id as string)
return arrayMove(columnOrder, 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={[restrictToHorizontalAxis]}
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}>
<SortableContext
items={columnOrder}
strategy={horizontalListSortingStrategy}
>
{headerGroup.headers.map(header => (
<DraggableTableHeader key={header.id} header={header} />
))}
</SortableContext>
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map(row => (
<tr key={row.id}>
{row.getVisibleCells().map(cell => (
<SortableContext
key={cell.id}
items={columnOrder}
strategy={horizontalListSortingStrategy}
>
<DragAlongCell key={cell.id} cell={cell} />
</SortableContext>
))}
</tr>
))}
</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>
)
