# React Drag and Drop with dnd kit

React drag and drop in AdminKit PRO: how the task board sorts and moves cards between columns with dnd kit, by mouse, touch and keyboard.

> This feature is only available in **AdminKit PRO**. [Learn more](/pricing/#react).

Drag and drop lets people put things in order by moving them: tasks in a column, items in a list, cards on a board. The task board of [AdminKit for React](/react/) does this with [dnd kit](https://dndkit.com/). You drag a task up or down within its column, or over to another column, with the mouse, a finger or the keyboard.

There is no wrapper component. The board uses dnd kit directly, and you use it the same way in your own components. AdminKit PRO installs three packages:

| Package | Version | Used for |
| --- | --- | --- |
| `@dnd-kit/core` | 6.3.1 | `DndContext`, sensors, `DragOverlay`, `useDroppable` |
| `@dnd-kit/sortable` | 10.0.0 | `SortableContext`, `useSortable`, `arrayMove` |
| `@dnd-kit/utilities` | 3.2.2 | `CSS.Translate.toString()` for the drag transform |

The docs cannot run dnd kit, so this page has code only. Try the board in the [tasks demo](https://react-demo.adminkit.io/pages-tasks).

## Example

The simplest case is a list people can reorder. This one uses the same sensors and drag styles as the task board:

```tsx
import {
  closestCorners,
  DndContext,
  type DragEndEvent,
  KeyboardSensor,
  MouseSensor,
  TouchSensor,
  useSensor,
  useSensors,
} from "@dnd-kit/core";
import {
  arrayMove,
  SortableContext,
  sortableKeyboardCoordinates,
  useSortable,
  verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { useState } from "react";
import { cx } from "@/lib/cx";

function SortableItem({ id }: { id: string }) {
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } =
    useSortable({ id });

  return (
    <div
      ref={setNodeRef}
      className={cx("list-group-item cursor-grab", isDragging && "sortable-ghost")}
      style={{ transform: CSS.Translate.toString(transform), transition }}
      {...attributes}
      {...listeners}
    >
      {id}
    </div>
  );
}

export function SortableList() {
  const [items, setItems] = useState([
    "Write release notes",
    "Review pull request",
    "Update dependencies",
  ]);
  const sensors = useSensors(
    useSensor(MouseSensor, { activationConstraint: { distance: 4 } }),
    useSensor(TouchSensor, { activationConstraint: { delay: 150, tolerance: 5 } }),
    useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
  );

  const onDragEnd = ({ active, over }: DragEndEvent) => {
    if (over && active.id !== over.id) {
      setItems((current) =>
        arrayMove(current, current.indexOf(String(active.id)), current.indexOf(String(over.id)))
      );
    }
  };

  return (
    <DndContext
      id="sortable-list"
      sensors={sensors}
      collisionDetection={closestCorners}
      onDragEnd={onDragEnd}
    >
      <SortableContext items={items} strategy={verticalListSortingStrategy}>
        <div className="list-group">
          {items.map((item) => (
            <SortableItem key={item} id={item} />
          ))}
        </div>
      </SortableContext>
    </DndContext>
  );
}
```

`.sortable-ghost` fades the item being dragged to 40% opacity, and `.cursor-grab` gives it the grab cursor. Both classes are in AdminKit's Sass.

## How the task board works

The board is in `src/components/pages/apps/tasks/task-board.tsx`, and its starting data come from `src/mocks/tasks.ts`. It keeps two pieces of state: the tasks by key, and the order of the task keys in each column, a `Record<ColumnId, string[]>`.

| Part | What it does |
| --- | --- |
| `DndContext` | One for the whole board, with `closestCorners` collision detection |
| Sensors | Mouse after 4px of movement; touch after a 150ms press (5px tolerance), so a swipe still scrolls the board; keyboard with `sortableKeyboardCoordinates` |
| `SortableContext` | One per column, with the column's task keys and `verticalListSortingStrategy` |
| `useDroppable` | Makes each column's list a drop target, so a task can be dropped in an empty column |
| `useSortable` | Makes each card draggable; the transition is 150ms `ease`, or none under `prefers-reduced-motion` |
| `DragOverlay` | Shows a copy of the card under the pointer (`.sortable-drag`), in a portal on `document.body`; no drop animation under reduced motion |

Four handlers move the keys around:

- `onDragStart` remembers the board as it was.
- `onDragOver` moves the task to another column as soon as it is over that column, so the cards there make room.
- `onDragEnd` reorders within the column with `arrayMove`.
- `onDragCancel` runs when the user presses Escape. It puts the board back as it was at the start.

Vite builds a single-page app that only renders in the browser. So the board can pass `document.body` to `createPortal` for the overlay without checking that it exists.

## Accessibility

- The keyboard sensor makes every card usable without a mouse: Tab to a card, Space or Enter to pick it up, the arrow keys to move it within or between columns, Space or Enter to drop it and Escape to cancel.
- Through its `accessibility` prop, `DndContext` gets `screenReaderInstructions` with these keys, and `announcements` that name the task and its place, such as "Picked up …, in To do, position 2 of 5."
- The cards stay `<article>` elements. The board passes only `tabIndex`, `aria-roledescription` and `aria-describedby` from `useSortable`'s `attributes`, not its `role="button"`. In your own lists, spread all `attributes` unless the element has a role to keep.
- Under `prefers-reduced-motion`, the cards and the overlay move without animation. The board checks this with `useReducedMotion()` from `@/hooks/use-media-query`.

## Related

- [Next.js Drag and Drop with dnd kit](/nextjs/docs/plugins/drag-and-drop/): the same board in AdminKit for Next.js
- [Bootstrap 5 drag and drop](/bootstrap/docs/plugins/drag-and-drop/): the HTML version, with SortableJS
- [Card](/react/docs/components/card/): the cards of the task board
- [Dropdowns](/react/docs/components/dropdowns/): the column menus of the board
- [Tasks demo](https://react-demo.adminkit.io/pages-tasks)

---

Canonical HTML page: https://adminkit.io/react/docs/plugins/drag-and-drop/

Site overview for agents: https://adminkit.io/llms.txt (expanded: https://adminkit.io/llms-full.txt) · Documentation: https://adminkit.io/bootstrap/docs/ · Questions: support@adminkit.io
