Drag row
Drag to reorder a short list by a handle in the row's left gutter, on native HTML5 drag and drop.
bunx shadcn@latest add @nalarx/drag-rowUsage
useReorder holds the drag state for one list; reorderById computes the new order on drop. Leave gutter (22px) of room left of the list for the handle.
import { DragRow, reorderById, useReorder } from "@/components/ui/nx/DragRow";
const api = useReorder((fromId, toId) =>
setItems((list) => reorderById(list, (item) => item.id, fromId, toId)),
);
<div className="pl-5.5">
{items.map((item) => (
<DragRow key={item.id} id={item.id} api={api} label={t("list.move", { name: item.name })}>
<ItemRow item={item} />
</DragRow>
))}
</div>Only the handle starts a drag, so text and buttons inside a row keep working; the whole row is the drop target, outlined while a row hovers over it. The handle shows on hover (alwaysShowHandle keeps it visible) and drags a copy of the whole row.
Hover a row, then drag its handle:
Keyboard
There is no keyboard reordering, and no touch support: native HTML5 drag and drop works with a mouse or trackpad only. The handle is not focusable and is hidden from screen readers. Where keyboard or touch users must reorder, give them another way (move up and down buttons, or a position field).
Props
DragRow:
| Prop | Type | Default | |
|---|---|---|---|
id | string | The row's id, as reorderById reads it. | |
api | ReorderApi | From useReorder. | |
label | string | The handle's tooltip. | |
gutter | number | 22 | Gutter width in px. |
alwaysShowHandle | boolean | false | For rows that carry other rows, such as a section header. |
useReorder(onMove) returns { dragId, overId, handleProps(id), dropProps(id) } for building your own row. reorderById(items, idOf, fromId, toId) is pure: it returns a new array and leaves the order alone for unknown ids. setRowDragImage(event, row) sets a whole-row drag image.
Replaces
Web DragRow.tsx (DragRow, useReorder, reorderById, setRowDragImage, ReorderApi) and its test.