Skip to main content
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

Build docs developers (and LLMs) love