Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/clauderic/dnd-kit/llms.txt

Use this file to discover all available pages before exploring further.

Sortable lists combine drag and drop functionality with automatic reordering. The useSortable hook provides everything you need to build sortable interfaces.

Basic Sortable List

Create a simple sortable list by combining DragDropProvider with useSortable:
import {useState} from 'react';
import {DragDropProvider} from '@dnd-kit/react';
import {useSortable} from '@dnd-kit/react/sortable';
import {move} from '@dnd-kit/helpers';

function SortableItem({id, index}) {
  const [element, setElement] = useState(null);
  const {isDragging} = useSortable({id, index, element});

  return (
    <div
      ref={setElement}
      style={{
        opacity: isDragging ? 0.5 : 1,
        padding: '12px 20px',
        border: '2px solid #4c9ffe',
        borderRadius: '8px',
        background: '#e8f0fe',
        cursor: 'grab',
      }}
    >
      {id}
    </div>
  );
}

function App() {
  const [items, setItems] = useState([1, 2, 3, 4, 5]);

  return (
    <DragDropProvider
      onDragEnd={(event) => {
        setItems((items) => move(items, event));
      }}
    >
      <div style={{display: 'flex', flexDirection: 'column', gap: 18}}>
        {items.map((id, index) => (
          <SortableItem key={id} id={id} index={index} />
        ))}
      </div>
    </DragDropProvider>
  );
}
The move helper automatically calculates the new array order based on the drag operation.

Sortable Grid

Build a sortable grid by adjusting the container layout:
function GridSortable({id, index}) {
  const [element, setElement] = useState(null);
  const {isDragging} = useSortable({id, index, element});

  return (
    <div
      ref={setElement}
      style={{
        height: 150,
        opacity: isDragging ? 0.5 : 1,
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        border: '2px solid #4c9ffe',
        borderRadius: '8px',
        background: '#e8f0fe',
      }}
    >
      {id}
    </div>
  );
}

function App() {
  const [items, setItems] = useState(Array.from({length: 20}, (_, i) => i + 1));

  return (
    <DragDropProvider onDragEnd={(event) => setItems((items) => move(items, event))}>
      <div
        style={{
          display: 'grid',
          gridTemplateColumns: 'repeat(auto-fill, 150px)',
          gridAutoRows: 150,
          gap: 18,
          maxWidth: 900,
          margin: '0 auto',
        }}
      >
        {items.map((id, index) => (
          <GridSortable key={id} id={id} index={index} />
        ))}
      </div>
    </DragDropProvider>
  );
}

Using the Sortable Hook

The useSortable hook returns several useful properties and refs:
const {
  sortable,        // The sortable instance
  isDragging,      // True when this item is being dragged
  isDropping,      // True during drop animation
  isDragSource,    // True when this is the drag source
  isDropTarget,    // True when this is a drop target
  ref,             // Ref for the draggable element
  handleRef,       // Ref for a drag handle (optional)
  targetRef,       // Ref for custom drop target
} = useSortable({id, index, element});

Drag Handles

Add a drag handle to make only part of the item draggable:
function SortableWithHandle({id, index}) {
  const [element, setElement] = useState(null);
  const [handle, setHandle] = useState(null);
  const {isDragging} = useSortable({id, index, element, handle});

  return (
    <div
      ref={setElement}
      style={{
        opacity: isDragging ? 0.5 : 1,
        padding: 12,
        border: '2px solid #4c9ffe',
        borderRadius: 8,
        display: 'flex',
        alignItems: 'center',
        gap: 12,
      }}
    >
      <button
        ref={setHandle}
        style={{
          cursor: 'grab',
          padding: '4px 8px',
          background: '#4c9ffe',
          border: 'none',
          borderRadius: 4,
          color: 'white',
        }}
      >
        ⋮⋮
      </button>
      <span>Item {id}</span>
    </div>
  );
}
Use drag handles when items contain interactive elements like buttons or inputs. This prevents conflicts between dragging and clicking.

Multi-Container Sorting

Create sortable items that can move between different lists using groups:
function MultiContainerApp() {
  const [containers, setContainers] = useState({
    A: [1, 2, 3],
    B: [4, 5, 6],
  });

  const handleDragEnd = (event) => {
    if (event.canceled) return;

    const {source, target} = event.operation;
    if (!source || !target) return;

    setContainers((containers) => {
      // Move item between or within containers
      const sourceGroup = source.group;
      const targetGroup = target.group;
      
      if (sourceGroup === targetGroup) {
        // Same container - reorder
        return {
          ...containers,
          [sourceGroup]: move(containers[sourceGroup], event),
        };
      } else {
        // Different containers - move between them
        const sourceItems = [...containers[sourceGroup]];
        const targetItems = [...containers[targetGroup]];
        const [movedItem] = sourceItems.splice(source.index, 1);
        targetItems.splice(target.index, 0, movedItem);
        
        return {
          ...containers,
          [sourceGroup]: sourceItems,
          [targetGroup]: targetItems,
        };
      }
    });
  };

  return (
    <DragDropProvider onDragEnd={handleDragEnd}>
      <div style={{display: 'flex', gap: 24}}>
        {Object.entries(containers).map(([group, items]) => (
          <div key={group} style={{flex: 1}}>
            <h3>Container {group}</h3>
            <div style={{display: 'flex', flexDirection: 'column', gap: 12}}>
              {items.map((id, index) => (
                <SortableItem key={id} id={id} index={index} group={group} />
              ))}
            </div>
          </div>
        ))}
      </div>
    </DragDropProvider>
  );
}

function SortableItem({id, index, group}) {
  const [element, setElement] = useState(null);
  const {isDragging} = useSortable({id, index, element, group});

  return (
    <div
      ref={setElement}
      style={{
        opacity: isDragging ? 0.5 : 1,
        padding: 12,
        border: '2px solid #4c9ffe',
        borderRadius: 8,
      }}
    >
      Item {id}
    </div>
  );
}

Disabling Sortable Items

Disable specific items from being dragged or accepting drops:
function SortableItem({id, index, disabled}) {
  const [element, setElement] = useState(null);
  const {isDragging} = useSortable({
    id,
    index,
    element,
    disabled,  // Prevents both dragging and dropping
  });

  return (
    <div
      ref={setElement}
      style={{
        opacity: disabled ? 0.3 : isDragging ? 0.5 : 1,
        cursor: disabled ? 'not-allowed' : 'grab',
      }}
    >
      {id}
    </div>
  );
}

Custom Data

Attach custom data to sortable items:
const {isDragging} = useSortable({
  id,
  index,
  element,
  data: {
    category: 'important',
    priority: 1,
    metadata: {...},
  },
});
Access this data in event handlers:
<DragDropProvider
  onDragEnd={(event) => {
    const sourceData = event.operation.source?.data;
    const targetData = event.operation.target?.data;
    console.log('Source category:', sourceData?.category);
  }}
>
Always provide a unique id for each sortable item. Using array indices as IDs can cause issues when items are added or removed.

Advanced: Separate Source and Target

For complex layouts, you can specify different elements for the draggable source and drop target:
function SortableItem({id, index}) {
  const [source, setSource] = useState(null);
  const [target, setTarget] = useState(null);
  const {isDragging} = useSortable({
    id,
    index,
    element: source,  // Element to drag
    target,           // Element to drop onto
  });

  return (
    <div ref={setTarget} style={{padding: 4, border: '1px dashed #ccc'}}>
      <div
        ref={setSource}
        style={{
          padding: 12,
          background: '#e8f0fe',
          opacity: isDragging ? 0.5 : 1,
        }}
      >
        Item {id}
      </div>
    </div>
  );
}
This pattern is useful when you need visual spacing around drop zones.

Next Steps

1

Customize Transitions

Learn how to configure animations and transitions for smooth sorting effects
2

Add Accessibility

Ensure your sortable lists are accessible with keyboard navigation
3

Optimize Performance

Improve rendering performance for large lists with optimization techniques