Skip to content
Drag & Drop

dnd kit — Component Library Guide

Lightweight, performant, accessible drag and drop toolkit for React. Supports sorting, multi-containers, virtualization, and custom sensors.

TypeScript MIT
1.7M weekly downloads
13.2k GitHub stars
~18kB bundle size
v6.3.1 latest version
drag-and-dropsortableaccessibletouchvirtual

TL;DR: dnd kit is the modern drag-and-drop toolkit for React 18. It supports pointer, mouse, touch, and keyboard sensors, works with virtual lists, and replaces the unmaintained react-beautiful-dnd. More setup than its predecessor, but far more flexible. Official docs: dndkit.com.

What is dnd kit?

dnd kit is the modern replacement for react-beautiful-dnd (now unmaintained). It's built from the ground up for React 18, supports pointer, mouse, touch, and keyboard sensors, and works with virtual lists. The API is lower-level than its predecessor - more flexible, but requires more setup for common use cases.

When to use it

Use dnd kit for any drag-and-drop interaction in React:

  • Sortable lists and kanban boards
  • File reordering
  • Dashboard widget rearrangement
  • Multi-container dragging (drag between columns)

If you were using react-beautiful-dnd and it's now breaking on React 18, dnd kit is the migration path.

Key Features

  • Sensors - Pointer (mouse/touch), Keyboard, and custom sensors
  • @dnd-kit/sortable - higher-level package for sortable lists with smooth animations
  • Multi-container - drag items between separate droppable areas
  • Collision detection - pluggable algorithms (closest center, closest corners, pointer within)
  • Accessibility - screen reader announcements built in
  • Virtual list support - works with TanStack Virtual

Installation

npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities

Sortable List

import {
  DndContext,
  closestCenter,
  KeyboardSensor,
  PointerSensor,
  useSensor,
  useSensors,
  type DragEndEvent,
} from '@dnd-kit/core'
import {
  SortableContext,
  sortableKeyboardCoordinates,
  verticalListSortingStrategy,
  useSortable,
  arrayMove,
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { useState } from 'react'

function SortableItem({ id, label }: { id: string; label: string }) {
  const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id })
  const style = {
    transform: CSS.Transform.toString(transform),
    transition,
  }
  return (
    <div ref={setNodeRef} style={style} {...attributes} {...listeners}
      className="p-3 bg-white border rounded-lg cursor-grab active:cursor-grabbing"
    >
      {label}
    </div>
  )
}

function SortableList() {
  const [items, setItems] = useState([
    { id: '1', label: 'Item 1' },
    { id: '2', label: 'Item 2' },
    { id: '3', label: 'Item 3' },
  ])

  const sensors = useSensors(
    useSensor(PointerSensor),
    useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
  )

  function handleDragEnd(event: DragEndEvent) {
    const { active, over } = event
    if (over && active.id !== over.id) {
      setItems(items => {
        const oldIndex = items.findIndex(i => i.id === active.id)
        const newIndex = items.findIndex(i => i.id === over.id)
        return arrayMove(items, oldIndex, newIndex)
      })
    }
  }

  return (
    <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
      <SortableContext items={items.map(i => i.id)} strategy={verticalListSortingStrategy}>
        <div className="flex flex-col gap-2">
          {items.map(item => (
            <SortableItem key={item.id} id={item.id} label={item.label} />
          ))}
        </div>
      </SortableContext>
    </DndContext>
  )
}

TypeScript Tips

Type your drag data for cross-container scenarios:

interface DragData {
  type: 'task'
  columnId: string
}

// Access in event handlers:
function handleDragEnd(event: DragEndEvent) {
  const data = event.active.data.current as DragData | undefined
  if (data?.type === 'task') { /* ... */ }
}

Common Gotchas

  • id must be a string or number - don't use object references as IDs
  • useSortable must be inside SortableContext which must be inside DndContext
  • Drag overlay - for complex items, use DragOverlay to show a custom dragging preview that follows the pointer, instead of the original item
  • Touch conflict with scroll - use activationConstraint on PointerSensor to distinguish scroll intent from drag intent on mobile

Building a kanban board with multiple columns

The single sortable list is the easy case. Real apps usually need items that move between containers, like a kanban board where cards jump from "To Do" to "In Progress". dnd kit handles this, but you have to do a bit of bookkeeping yourself.

The trick is attaching metadata to each draggable and each droppable through the data option. When onDragOver or onDragEnd fires, you read that metadata to figure out which column the card came from and which column it landed in. dnd kit won't move the item across arrays for you - it only tells you what happened. You own the state update.

const { setNodeRef } = useDroppable({
  id: columnId,
  data: { type: 'column', columnId },
})

A common mistake is forgetting that an empty column has no items to collide with. If a column can be empty, register the column container itself as a droppable so cards can still land there. Without that, dropping into an empty column does nothing and users get frustrated fast.

Accessibility is the real selling point

Most drag-and-drop on the web is mouse-only, which locks out keyboard and screen reader users entirely. dnd kit ships keyboard support out of the box through the KeyboardSensor. Users tab to an item, press space to pick it up, move with arrow keys, and press space again to drop. That alone covers a huge accessibility gap.

It also fires live region announcements as items move, so a screen reader can say things like "Item 2 moved to position 3 of 5". You can customize these strings through the announcements prop on DndContext. If you ship drag-and-drop in a product that has to meet WCAG, this is the library that gets you there without writing the ARIA plumbing yourself.

Performance on long and virtual lists

dnd kit avoids re-rendering the whole list during a drag. It tracks the active item and applies transforms only where needed. For lists in the low thousands you'll be fine without virtualization.

Once you cross into very large lists, pair it with a windowing library. The catch is that virtualization unmounts off-screen items, so the drop target you're aiming for might not exist in the DOM yet. You handle this with a custom collision detection function plus auto-scroll near the viewport edges. It's more work, but it's possible, and the official docs include a worked example. In my experience, most teams never need this - measure first before reaching for it.

Sensors and activation constraints

The activationConstraint option is the unsung hero for touch devices. Set a small distance (say 8 pixels) or a delay, and a quick tap won't accidentally start a drag. This is the difference between a list that scrolls naturally on a phone and one that grabs cards every time the user tries to swipe past them. Test on a real device - emulators lie about touch behavior.

  • Framer Motion - add layout prop to list items for smooth animated reordering as dnd kit updates array order
  • TanStack Virtual - virtualised sortable lists require custom collision detection; dnd kit supports this with the DndContext API

Frequently Asked Questions

Is dnd kit a replacement for react-beautiful-dnd?

Yes. react-beautiful-dnd is no longer actively maintained and has known issues with React 18 Strict Mode. dnd kit is the recommended migration path. The API is different and more flexible, but dnd kit covers all the same use cases and more.

Does dnd kit support touch screens and mobile devices?

Yes. dnd kit supports mouse, touch, and keyboard sensors out of the box. Use the PointerSensor which handles both mouse and touch events. For mobile, consider adding an activationConstraint to distinguish tap from scroll.

What is the difference between @dnd-kit/core and @dnd-kit/sortable?

@dnd-kit/core provides the low-level primitives: DndContext, useDraggable, and useDroppable. @dnd-kit/sortable builds on top of core to provide higher-level sortable list functionality with useSortable, SortableContext, and arrayMove. Most use cases only need the sortable package.

How do I show a drag overlay instead of moving the original element?

Use the DragOverlay component from @dnd-kit/core. Render your custom preview inside DragOverlay conditionally when activeId is set. The original item remains in place while dragging and DragOverlay follows the pointer.

Can dnd kit work with virtualised lists?

Yes. dnd kit is designed to work with TanStack Virtual and similar virtualisation libraries. The key requirement is that you must use custom collision detection and handle the case where the drag target is outside the rendered viewport.