Claude Code for dnd kit: Drag and Drop in React — Claude Skills 360 Blog
Blog / Frontend / Claude Code for dnd kit: Drag and Drop in React
Frontend

Claude Code for dnd kit: Drag and Drop in React

Published: March 14, 2027
Read time: 8 min read
By: Claude Skills 360

dnd kit provides accessible drag and drop for React — it ships without HTML5 drag and drop API limitations, supports touch and pointer events, and includes ARIA announcements for keyboard users. DndContext wraps the interactive area and provides onDragStart, onDragOver, and onDragEnd callbacks. useDraggable({ id }) makes any element draggable; useDroppable({ id }) defines drop targets. SortableContext manages ordered lists; useSortable({ id }) combines drag and drop in one hook for list items. DragOverlay renders a portal overlay for smooth visual feedback during drag. closestCorners and closestCenter are the standard collision detection strategies. Sensors handle mouse, touch, pointer, and keyboard input. Claude Code generates dnd kit implementations for sortable lists, kanban boards, file uploaders, and multi-container drag-and-drop interfaces.

CLAUDE.md for dnd kit

## dnd kit Stack
- Version: @dnd-kit/core >= 6.3, @dnd-kit/sortable >= 8.0, @dnd-kit/utilities
- DndContext: wrap all draggable/droppable elements — onDragStart/Over/End callbacks
- Sortable: <SortableContext items={ids} strategy={verticalListSortingStrategy}>
- useSortable: const { setNodeRef, attributes, listeners, transform, isDragging } = useSortable({ id })
- Transform: style={{ transform: CSS.Translate.toString(transform) }} — avoid scale during sort
- Overlay: <DragOverlay> renders dragged clone as portal — prevents layout shift
- arrayMove: from @dnd-kit/sortable — reorder array: arrayMove(items, oldIndex, newIndex)
- Collision: closestCorners for kanban; closestCenter for single lists

Sortable List

// components/dnd/SortableOrdersList.tsx — vertical sortable list
"use client"
import {
  DndContext,
  closestCenter,
  KeyboardSensor,
  PointerSensor,
  useSensor,
  useSensors,
  DragOverlay,
  type DragEndEvent,
  type DragStartEvent,
} from "@dnd-kit/core"
import {
  SortableContext,
  sortableKeyboardCoordinates,
  useSortable,
  verticalListSortingStrategy,
  arrayMove,
} from "@dnd-kit/sortable"
import { CSS } from "@dnd-kit/utilities"
import { useState } from "react"
import { GripVertical } from "lucide-react"

interface Task {
  id: string
  title: string
  priority: "low" | "medium" | "high"
}

function SortableTaskItem({ task }: { task: Task }) {
  const {
    attributes,
    listeners,
    setNodeRef,
    transform,
    transition,
    isDragging,
  } = useSortable({ id: task.id })

  const style = {
    transform: CSS.Transform.toString(transform),
    transition,
    opacity: isDragging ? 0.5 : 1,
  }

  const priorityColors = {
    low: "bg-slate-100 text-slate-700",
    medium: "bg-amber-100 text-amber-700",
    high: "bg-red-100 text-red-700",
  }

  return (
    <div
      ref={setNodeRef}
      style={style}
      className="flex items-center gap-3 rounded-lg border bg-white p-3 shadow-sm"
    >
      <button
        {...attributes}
        {...listeners}
        className="cursor-grab active:cursor-grabbing text-muted-foreground hover:text-foreground touch-none"
        aria-label="Drag to reorder"
      >
        <GripVertical className="h-4 w-4" />
      </button>
      <span className="flex-1 text-sm font-medium">{task.title}</span>
      <span className={`rounded-full px-2 py-0.5 text-xs font-medium ${priorityColors[task.priority]}`}>
        {task.priority}
      </span>
    </div>
  )
}

function TaskCardOverlay({ task }: { task: Task }) {
  return (
    <div className="flex items-center gap-3 rounded-lg border bg-white p-3 shadow-xl ring-2 ring-primary">
      <GripVertical className="h-4 w-4 text-muted-foreground" />
      <span className="flex-1 text-sm font-medium">{task.title}</span>
    </div>
  )
}

export function SortableTasksList({
  initialTasks,
  onReorder,
}: {
  initialTasks: Task[]
  onReorder: (tasks: Task[]) => void
}) {
  const [tasks, setTasks] = useState(initialTasks)
  const [activeTask, setActiveTask] = useState<Task | null>(null)

  const sensors = useSensors(
    useSensor(PointerSensor, {
      activationConstraint: { distance: 8 },  // Require 8px movement before drag starts
    }),
    useSensor(KeyboardSensor, {
      coordinateGetter: sortableKeyboardCoordinates,
    })
  )

  function handleDragStart({ active }: DragStartEvent) {
    setActiveTask(tasks.find(t => t.id === active.id) ?? null)
  }

  function handleDragEnd({ active, over }: DragEndEvent) {
    setActiveTask(null)
    if (!over || active.id === over.id) return

    setTasks(current => {
      const oldIndex = current.findIndex(t => t.id === active.id)
      const newIndex = current.findIndex(t => t.id === over.id)
      const reordered = arrayMove(current, oldIndex, newIndex)
      onReorder(reordered)
      return reordered
    })
  }

  return (
    <DndContext
      sensors={sensors}
      collisionDetection={closestCenter}
      onDragStart={handleDragStart}
      onDragEnd={handleDragEnd}
    >
      <SortableContext items={tasks.map(t => t.id)} strategy={verticalListSortingStrategy}>
        <div className="space-y-2">
          {tasks.map(task => (
            <SortableTaskItem key={task.id} task={task} />
          ))}
        </div>
      </SortableContext>

      <DragOverlay>
        {activeTask && <TaskCardOverlay task={activeTask} />}
      </DragOverlay>
    </DndContext>
  )
}

Kanban Board — Multi-Container

// components/dnd/KanbanBoard.tsx — drag between columns
"use client"
import {
  DndContext,
  DragOverlay,
  closestCorners,
  useSensor,
  useSensors,
  PointerSensor,
  KeyboardSensor,
  type DragEndEvent,
  type DragOverEvent,
  type DragStartEvent,
} from "@dnd-kit/core"
import {
  SortableContext,
  sortableKeyboardCoordinates,
  useSortable,
  verticalListSortingStrategy,
  arrayMove,
} from "@dnd-kit/sortable"
import { useDroppable } from "@dnd-kit/core"
import { CSS } from "@dnd-kit/utilities"
import { useState } from "react"

type Status = "todo" | "in_progress" | "done"

interface Card {
  id: string
  title: string
  status: Status
}

const COLUMNS: { id: Status; label: string }[] = [
  { id: "todo", label: "To Do" },
  { id: "in_progress", label: "In Progress" },
  { id: "done", label: "Done" },
]

function KanbanCard({ card }: { card: Card }) {
  const { setNodeRef, attributes, listeners, transform, transition, isDragging } =
    useSortable({ id: card.id, data: { card, type: "card" } })

  return (
    <div
      ref={setNodeRef}
      style={{
        transform: CSS.Transform.toString(transform),
        transition,
        opacity: isDragging ? 0 : 1,
      }}
      className="rounded-lg bg-white border p-3 shadow-sm cursor-grab active:cursor-grabbing"
      {...attributes}
      {...listeners}
    >
      <p className="text-sm font-medium">{card.title}</p>
    </div>
  )
}

function KanbanColumn({ status, cards, label }: { status: Status; cards: Card[]; label: string }) {
  const { setNodeRef, isOver } = useDroppable({ id: status })

  return (
    <div
      ref={setNodeRef}
      className={`rounded-xl p-3 min-h-[400px] w-72 transition-colors ${
        isOver ? "bg-blue-50 ring-2 ring-blue-200" : "bg-muted"
      }`}
    >
      <h3 className="font-semibold text-sm mb-3 flex items-center justify-between">
        {label}
        <span className="bg-background rounded-full px-2 py-0.5 text-xs">{cards.length}</span>
      </h3>
      <SortableContext items={cards.map(c => c.id)} strategy={verticalListSortingStrategy}>
        <div className="space-y-2">
          {cards.map(card => (
            <KanbanCard key={card.id} card={card} />
          ))}
        </div>
      </SortableContext>
    </div>
  )
}

export function KanbanBoard({ initialCards }: { initialCards: Card[] }) {
  const [cards, setCards] = useState(initialCards)
  const [activeCard, setActiveCard] = useState<Card | null>(null)

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

  function handleDragStart({ active }: DragStartEvent) {
    setActiveCard(cards.find(c => c.id === active.id) ?? null)
  }

  function handleDragOver({ active, over }: DragOverEvent) {
    if (!over) return

    const activeCard = cards.find(c => c.id === active.id)!
    const overStatus = (COLUMNS.find(col => col.id === over.id)?.id ??
      cards.find(c => c.id === over.id)?.status) as Status | undefined

    if (overStatus && activeCard.status !== overStatus) {
      setCards(prev =>
        prev.map(c => c.id === active.id ? { ...c, status: overStatus } : c)
      )
    }
  }

  function handleDragEnd({ active, over }: DragEndEvent) {
    setActiveCard(null)
    if (!over || active.id === over.id) return

    const overCard = cards.find(c => c.id === over.id)
    if (!overCard) return

    setCards(prev => {
      const oldIndex = prev.findIndex(c => c.id === active.id)
      const newIndex = prev.findIndex(c => c.id === over.id)
      return arrayMove(prev, oldIndex, newIndex)
    })
  }

  return (
    <DndContext
      sensors={sensors}
      collisionDetection={closestCorners}
      onDragStart={handleDragStart}
      onDragOver={handleDragOver}
      onDragEnd={handleDragEnd}
    >
      <div className="flex gap-4 overflow-x-auto pb-4">
        {COLUMNS.map(col => (
          <KanbanColumn
            key={col.id}
            status={col.id}
            label={col.label}
            cards={cards.filter(c => c.status === col.id)}
          />
        ))}
      </div>

      <DragOverlay>
        {activeCard && (
          <div className="rounded-lg bg-white border p-3 shadow-2xl ring-2 ring-primary rotate-3 w-72">
            <p className="text-sm font-medium">{activeCard.title}</p>
          </div>
        )}
      </DragOverlay>
    </DndContext>
  )
}

For the React Beautiful DnD alternative that is now unmaintained (archived by Atlassian) but was the previous standard for kanban-style drag-and-drop with opinionated beautiful animations — dnd kit is the recommended replacement with better maintained code and accessibility support, see the migration guide patterns. For native HTML5 drag and drop when browser compatibility is the priority and complex touch support or accessibility aren’t required — useful for simple file drop targets or basic draggable elements without the full dnd kit overhead, see the file upload patterns guide. The Claude Skills 360 bundle includes dnd kit skill sets covering sortable lists, kanban boards, and multi-container drag. Start with the free tier to try drag-and-drop implementation generation.

Keep Reading

Frontend

Claude Code for Chart.js Advanced: Custom Plugins and Mixed Charts

Advanced Chart.js patterns with Claude Code — chart.register() for tree-shaking, mixed chart types combining bar and line, custom plugin API with beforeDraw and afterDatasetsDraw hooks, ScriptableContext for computed colors, ChartDataLabels plugin for value labels, chartjs-plugin-zoom for pan and zoom, custom gradient fills via ctx.createLinearGradient, ChartJS annotation plugin for threshold lines, streaming data with chartjs-plugin-streaming, and react-chartjs-2 with useRef and chart instance.

6 min read Jun 27, 2027
Frontend

Claude Code for Nivo: Rich SVG and Canvas Charts

Build rich data visualizations with Nivo and Claude Code — ResponsiveLine and ResponsiveBar for adaptive charts, ResponsiveHeatMap for matrix data, ResponsiveTreeMap for hierarchal data, ResponsiveSunburst for nested proportions, ResponsiveChord for relationship diagrams, ResponsiveCalendar for activity heat maps, ResponsiveNetwork for force graphs, NivoTheme for consistent styling, tooltip customization with sliceTooltip, and motion config for spring animations.

6 min read Jun 26, 2027
Frontend

Claude Code for Victory Charts: React Native and Web Charts

Build cross-platform charts with Victory and Claude Code — VictoryChart, VictoryLine, VictoryBar, and VictoryScatter for web and React Native, VictoryPie for donut charts, VictoryArea for stacked areas, VictoryAxis for custom axes, VictoryTooltip and VictoryVoronoiContainer for hover tooltips, VictoryBrushContainer for range selection, VictoryZoomContainer for pan and zoom, VictoryLegend for series labels, custom theme with VictoryTheme, and VictoryStack for grouped bars.

6 min read Jun 25, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free