Column Reorder Example
Each header carries a GripVertical icon at the start and a transparent drop zone across the rest. Drag a grip over another header to move the source column before or after the target.
The shadcn components import from @virtuoso.dev/data-table/column-reorder — consumers that never reorder columns don’t ship that reducer. The grip is a Lucide icon, sized and spaced like the other shadcn header affordances so it visually separates from the column title.
APIs used
Section titled “APIs used”ReorderGrip— draggable header affordanceReorderDropZone— header drop targetHeaderStart/HeaderOverlay— slot positions for the components
import { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderOverlay, HeaderStart } from '@/components/ui/data-table'
import { ReorderDropZone, ReorderGrip } from '@/components/ui/data-table/column-reorder'
import { localModel } from '@virtuoso.dev/data-table'
import { products } from './data'
const model = localModel({ data: products })
const columns = [
['name', 'Product', 'font-medium'],
['category', 'Category', ''],
['status', 'Status', ''],
['region', 'Region', ''],
] as const
export default function App() {
return (
<DataTable className="rounded-xl" computeRowKey={({ data }) => data.id} model={model} style={{ height: 400 }}>
{columns.map(([field, label, cellClassName]) => (
<DataTableColumn field={field} key={field}>
<DataTableColumnHeader>
<HeaderStart component={ReorderGrip} />
<HeaderOverlay component={ReorderDropZone} />
{() => label}
</DataTableColumnHeader>
<DataTableCell className={cellClassName}>{({ cellValue }) => String(cellValue)}</DataTableCell>
</DataTableColumn>
))}
</DataTable>
)
}Drag-and-drop is the spreadsheet-style affordance. For accessible alternatives or deterministic test setup, publish reorderColumns$ from an external menu or button — the streams accept the same operations the drag UI emits.