> ## Documentation Index
> Fetch the complete documentation index at: https://dndkit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sortable

> Reorder elements in a list or across multiple lists.

export const sortableStyles = `
body {
  padding: 1em;
  font-size: 20px;
  font-family: system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
  line-height: 1.5;
}

.list {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding: 0;
  margin: 0;
  list-style: none;
}

.item {
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  padding: 10px 20px;
  border: none;
  gap: 10px;
  background-color: rgb(255, 255, 255);
  border-radius: 6px;
  font-size: 14px !important;
  color: #555;
  outline: none;
  min-height: 50px;
  box-shadow: inset 0 0 1px rgba(0,0,0,0.4), 0 0 0 1px rgba(63, 63, 68, 0.05), 0 1px 2px 0 rgba(34, 33, 81, 0.05);
  font-family: var(--font-family);
  width: calc(25% - 10px);
  min-width: 150px;
  max-width: 300px;
  white-space: nowrap;
  transition: transform 0.3s ease, box-shadow 0.3s ease;
  cursor: grab;
}

.item[aria-grabbed="true"] {
  transform: scale(1.025);
  box-shadow: inset 0px 0px 1px rgba(0,0,0,0.5), -1px 0 15px 0 rgba(34, 33, 81, 0.01), 0px 15px 15px 0 rgba(34, 33, 81, 0.25)
}
`.trim();

export const CodeSandbox = ({files, height, hero, previewHeight, showTabs, template}) => {
  const [Editor, setEditor] = useState(null);
  useEffect(() => {
    import('@components/CodeSandbox').then(mod => {
      setEditor(() => mod.CodeSandbox);
    });
  }, []);
  if (!Editor) return null;
  return <div className={`not-prose${hero ? ' hero' : ''}`} style={previewHeight ? {
    '--preview-height': `${previewHeight}px`
  } : undefined}>
      <Editor files={files} height={height} showTabs={showTabs} template={template} />
    </div>;
};

export const Story = ({id, framework = "react", width = "100%", height = "250", hero = false}) => {
  const BRANCH = 'experimental';
  const STORYBOOKS = {
    react: {
      localPort: 6006,
      productionHost: '5fc05e08a4a65d0021ae0bf2'
    },
    vue: {
      localPort: 6008,
      productionHost: '6989440ed560d70abcd6bcc7'
    },
    vanilla: {
      localPort: 6007,
      productionHost: '69892d294eb9040f0d29aa81'
    },
    solid: {
      localPort: 6009,
      productionHost: '698944444eb9040f0d2a0217'
    },
    svelte: {
      localPort: 6010,
      productionHost: '69910d2a631cb57638616dcd'
    }
  };
  const config = STORYBOOKS[framework] ?? STORYBOOKS.react;
  const isDev = import.meta.env.DEV;
  const host = isDev ? `//localhost:${config.localPort}` : `https://${BRANCH}--${config.productionHost}.chromatic.com`;
  return <Frame>
      <iframe src={`${host}/iframe.html?args=&id=${id}&viewMode=story&hero=${hero}`} width={width} height={height} />
    </Frame>;
};

<Story id="react-sortable--example" height="320" hero />

## Usage

The `Sortable` class allows you to reorder elements in a list or across multiple lists. A sortable element is both [Droppable](/concepts/droppable) and [Draggable](/concepts/draggable), which means you can drag it and drop it to reorder.

First, create a [DragDropManager](/concepts/drag-drop-manager) instance and use it to create sortable items:

export const code = `
import {DragDropManager} from '@dnd-kit/dom';
import {Sortable} from '@dnd-kit/dom/sortable';

export function App() {
  const manager = new DragDropManager();

  const wrapper = document.createElement('ul');
  const items = ['Item 1', 'Item 2', 'Item 3', 'Item 4'];

  wrapper.classList.add('list');

  items.forEach((item, index) => {
    const element = document.createElement('li');

    element.classList.add('item');
    element.innerText = item;

    const sortable = new Sortable({
      id: item,
      index, // Required - the position in the list
      element,
    }, manager);

    wrapper.appendChild(element);
  });

  document.body.appendChild(wrapper);
}
`.trim();

<CodeSandbox
  files={{
'index.js': {code: `import './styles.css';\nimport {App} from './sortable.js';\n\nApp();`, hidden: true},
'sortable.js': {code, active: true},
'styles.css': {code: sortableStyles, hidden: true},
}}
  height={580}
  previewHeight={180}
  template="vanilla"
/>

## Multiple Lists

You can create multiple sortable lists by assigning sortable items to different groups:

```js theme={null}
const list1 = ['Item 1', 'Item 2'];
const list2 = ['Item 3', 'Item 4'];

// First list
list1.forEach((item, index) => {
  new Sortable({
    id: item,
    index,
    group: 'list1', // Assign to first group
    element: createItemElement(item),
  }, manager);
});

// Second list
list2.forEach((item, index) => {
  new Sortable({
    id: item,
    index,
    group: 'list2', // Assign to second group
    element: createItemElement(item),
  }, manager);
});
```

## Drag Handles

By default, the entire sortable element can be used to initiate dragging. You can restrict dragging to a specific handle element:

```js theme={null}
const element = document.createElement('li');
const handle = document.createElement('div');
handle.classList.add('handle');

element.appendChild(handle);

new Sortable({
  id: 'item-1',
  index: 0,
  element,
  handle, // Only allow dragging from the handle
}, manager);
```

## Animations

Sortable items automatically animate when their position changes. You can customize the animation through the `transition` option:

```js theme={null}
new Sortable({
  id: 'item-1',
  index: 0,
  transition: {
    duration: 250, // Animation duration in ms
    easing: 'cubic-bezier(0.25, 1, 0.5, 1)', // Animation easing
    idle: false, // Whether to animate when no drag is in progress
  }
}, manager);
```

## Optimistic Sorting

By default, every `Sortable` instance registers the `OptimisticSortingPlugin`. This plugin optimistically reorders DOM elements during a drag operation so that the UI feels responsive — without requiring your framework to re-render on every `dragover` event.

### How it works

When you drag a sortable item over another sortable item, the plugin:

1. Physically moves the DOM elements to reflect the new order.
2. Updates the `index` (and `group`, for multi-list scenarios) on each affected `Sortable` instance.
3. Sets the **drop target** to the drag source itself by calling `manager.actions.setDropTarget(source.id)`.

Step 3 has an important consequence: **during a drag, `source` and `target` on the operation will refer to the same element.** This also means that `isDragSource` and `isDropTarget` will both be `true` on the dragged item.

### Tracking position changes

Since `source` and `target` are the same, you cannot compare their IDs to determine what moved. Instead, use the sortable-specific properties available on the source:

| Property       | Description                                                          |
| -------------- | -------------------------------------------------------------------- |
| `index`        | The current position of the item (updated by the plugin as it moves) |
| `initialIndex` | The position the item was in when the drag started                   |
| `group`        | The current group the item belongs to                                |
| `initialGroup` | The group the item was in when the drag started                      |

### Preventing optimistic sorting for a single event

If you call `event.preventDefault()` in a `dragover` handler, the `OptimisticSortingPlugin` will skip the optimistic update for that particular event. This is useful when you want to handle certain moves yourself (for example, to prevent items from being dragged into a specific group) while still letting the plugin handle the rest:

```js theme={null}
manager.monitor.addEventListener('dragover', (event) => {
  const {source, target} = event.operation;

  if (shouldPreventMove(source, target)) {
    event.preventDefault(); // Optimistic sorting will not run for this event
  }
});
```

### Disabling optimistic sorting

If you prefer to manage sorting entirely in your application state (for example, by handling every `dragover` event), you can disable optimistic sorting by omitting the `OptimisticSortingPlugin` from the `plugins` array:

```js theme={null}
import {SortableKeyboardPlugin} from '@dnd-kit/dom/sortable';

new Sortable({
  id: 'item-1',
  index: 0,
  element,
  plugins: [SortableKeyboardPlugin], // No OptimisticSortingPlugin
}, manager);
```

Without optimistic sorting, `source` and `target` will be different elements during drag, and you can use their IDs directly. However, you will need to handle reordering in your `dragover` listener for smooth visual feedback.

## Managing state without the move helper

The `move` helper from `@dnd-kit/helpers` is a convenience function that takes your items and a drag event and returns a new array with the item moved to its new position. It supports flat arrays and grouped records, handles canceled drags, and works with optimistic sorting out of the box.

If you need more control over state updates, you can manage state manually using the sortable properties and the `isSortable` type guard.

### Single list

With optimistic sorting enabled (the default), you only need to handle the `dragend` event. The `isSortable` type guard narrows the `source` to expose `initialIndex` and `index`:

```js theme={null}
import {isSortable} from '@dnd-kit/dom/sortable';

manager.monitor.addEventListener('dragend', (event) => {
  if (event.canceled) return;

  const {source} = event.operation;

  if (isSortable(source)) {
    const {initialIndex, index} = source;

    if (initialIndex !== index) {
      // Reorder your data: move the item from initialIndex to index
      const newItems = [...items];
      const [removed] = newItems.splice(initialIndex, 1);
      newItems.splice(index, 0, removed);
      items = newItems;
    }
  }
});
```

### Multiple lists

For multiple lists, use `initialGroup` and `group` to detect whether the item stayed in the same list or moved to a different one:

```js theme={null}
manager.monitor.addEventListener('dragend', (event) => {
  if (event.canceled) return;

  const {source} = event.operation;

  if (isSortable(source)) {
    const {initialIndex, index, initialGroup, group} = source;

    if (initialGroup === group) {
      // Same group: reorder within the list
      const groupItems = [...items[group]];
      const [removed] = groupItems.splice(initialIndex, 1);
      groupItems.splice(index, 0, removed);
      items = {...items, [group]: groupItems};
    } else {
      // Cross-group transfer
      const sourceItems = [...items[initialGroup]];
      const [removed] = sourceItems.splice(initialIndex, 1);
      const targetItems = [...items[group]];
      targetItems.splice(index, 0, removed);
      items = {...items, [initialGroup]: sourceItems, [group]: targetItems};
    }
  }
});
```

## Type Guards

`@dnd-kit/dom/sortable` exports two type guards to help you work with sortable drag operations.

### `isSortable`

Checks whether a `Draggable` or `Droppable` instance is a sortable element. If it returns `true`, the type is narrowed to expose sortable-specific properties like `index`, `initialIndex`, `group`, and `initialGroup`.

```js theme={null}
import {isSortable} from '@dnd-kit/dom/sortable';

const {source} = event.operation;

if (isSortable(source)) {
  console.log(source.index);        // number
  console.log(source.initialIndex);  // number
  console.log(source.group);         // string | number | undefined
  console.log(source.initialGroup);  // string | number | undefined
}
```

### `isSortableOperation`

Checks whether **both** `source` and `target` of a drag operation are sortable elements. This is useful when you want to narrow the entire operation at once:

```js theme={null}
import {isSortableOperation} from '@dnd-kit/dom/sortable';

const {operation} = event;

if (isSortableOperation(operation)) {
  // Both source and target are narrowed to sortable types
  console.log(operation.source.initialIndex);
  console.log(operation.target.index);
}
```

Both type guards are also available from framework-specific packages:

* `@dnd-kit/react/sortable`
* `@dnd-kit/vue/sortable`
* `@dnd-kit/svelte/sortable`
* `@dnd-kit/solid/sortable`

## API Reference

### Arguments

The `Sortable` class accepts the following arguments:

<ParamField path="id" type="string | number" required>
  A unique identifier for this sortable item within the drag and drop manager.
</ParamField>

<ParamField path="index" type="number" required>
  The position of this item within its sortable group.
</ParamField>

<ParamField path="element" type="Element">
  The DOM element to make sortable. While not required in the constructor, it must be set to enable sorting.
</ParamField>

<ParamField path="group" type="string | number">
  Optionally assign this item to a group. Items can only be sorted within their group.
</ParamField>

<ParamField path="handle" type="Element">
  Optionally specify a drag handle element. If not provided, the entire element will be draggable.
</ParamField>

<ParamField path="target" type="Element">
  Optionally specify a different element to use as the drop target. By default, uses the main element.
</ParamField>

<ParamField path="transition" type="SortableTransition | null">
  Configure the animation when items are reordered:

  ```ts theme={null}
  interface SortableTransition {
    duration?: number; // Duration in ms (default: 250)
    easing?: string;  // CSS easing function (default: cubic-bezier)
    idle?: boolean;   // Animate when not dragging (default: false)
  }
  ```
</ParamField>

<ParamField path="disabled" type="boolean | SortableDisabled">
  Set to `true` to temporarily disable sorting for this item. Pass an object to disable dragging and dropping independently:

  ```ts theme={null}
  interface SortableDisabled {
    draggable?: boolean; // Prevent this item from being dragged
    droppable?: boolean; // Prevent other items from being dropped on this one
  }
  ```
</ParamField>

<ParamField path="type" type="string | number | Symbol">
  Optionally restrict which types of items can be sorted together.
</ParamField>

<ParamField path="accept" type="Type | Type[] | ((source: Draggable) => boolean)">
  Restrict which draggables can be dropped on this sortable item. Pass a single [`type`](#param-type), an array of types, or a predicate that receives the draggable and returns `true` to accept it.
</ParamField>

<ParamField path="modifiers" type="Modifier[]">
  An array of [modifiers](/extend/modifiers) to customize drag behavior.
</ParamField>

<ParamField path="sensors" type="Sensors[]">
  An array of [sensors](/extend/sensors) to detect drag interactions.
</ParamField>

<ParamField path="data" type="{[key: string]: any}">
  Optional data to associate with this sortable item, available in event handlers.
</ParamField>

### Properties

The `Sortable` instance provides these key properties:

* `index`: The current position in the list
* `group`: The assigned group identifier
* `isDragging`: Whether this item is currently being dragged
* `isDropTarget`: Whether this item is currently a drop target
* `disabled`: Whether sorting is disabled for this item. Returns a `boolean` when dragging and dropping share the same state, or a `{draggable, droppable}` object when they differ
* `element`: The main DOM element
* `target`: The drop target element (if different from main element)

### Methods

* `register()`: Register this sortable item with the manager
* `unregister()`: Remove this item from the manager
* `destroy()`: Clean up this sortable instance
* `accepts(draggable)`: Check if this item accepts a draggable
* `refreshShape()`: Recalculate the item's dimensions
