Sortable drag-drop list in React using HTML Drag Drop API

4 min read

β€’

Jan 9, 2026

Hi there! Welcome to my blog, this is Alan again πŸ˜‚.

Today, I’m gonna share with you how to create a Sortable drag-drop list just using HTML Drag Drop native API.

Sound interesting?

Let’s get started!

First, we need to initialize a project, use the below command to create a new one. For convenience, the command will generate a source code including NextJS, Charka UI, and many other useful for starting a project right away without extra steps.

Or you can use another tool such as create-react-app.

1npx @tinychange/new-next sortable-dragdrop-list

After the initialization, create src/pages/index.tsx for home page, and then add the below code to create an UI of a list of names

1
2import { Box } from '@chakra-ui/react'
3import { useState } from 'react'
4
5const DragItem = ({ name }: { name: string }) => {
6 return (
7 <Box
8 sx={{
9 padding: 2,
10 borderRadius: 2,
11 mb: 2,
12 border: '1px solid rgba(0,0,0,0.1)',
13 _hover: {
14 cursor: 'pointer',
15 },
16 }}
17 >
18 {name}
19 </Box>
20 )
21}
22
23export default function Home() {
24 const [data, setData] = useState([
25 'Alice',
26 'Bob',
27 'Charlie',
28 'Diana',
29 'Ethan',
30 'Fiona',
31 'George',
32 'Hannah',
33 'Isaac',
34 'Julia',
35 ])
36
37 return (
38 <Box
39 sx={{
40 border: '1px solid rgba(0,0,0,0.3)',
41 borderRadius: 4,
42 width: 300,
43 margin: 'auto',
44 padding: 4,
45 }}
46 >
47 {data.map((item) => (
48 <DragItem key={item} name={item} />
49 ))}
50 </Box>
51 )
52}

There is no complicated logic here, just a simple UI.

Press enter or click to view image in full size

Now we’re going into the main part, drag-drop.

Make the Name item draggable and when an item is being dragged, it should leave an empty space.

Update the DragItem component as below:

1import { DragEventHandler, useState } from 'react'
2
3const DragItem = ({ name }: { name: string }) => {
4 const [isDragging, setIsDragging] = useState(false)
5
6 return (
7 <Box
8 draggable
9 onDrag={() => setIsDragging(true)}
10 onDragEnd={() => setIsDragging(false)}
11 onDragOver={(evt) => evt.preventDefault()}
12 sx={{
13 padding: 2,
14 borderRadius: 2,
15 mb: 2,
16 border: '1px solid rgba(0,0,0,0.1)',
17 opacity: isDragging ? 0 : 1,
18 _hover: {
19 cursor: 'pointer',
20 },
21 }}
22 >
23 {name}
24 </Box>
25 )
26}
27

Press enter or click to view image in full size

Awesome!

Next, we need to move other items when the item being dragged goes over any other. The expected behavior will be:

  • Let’s say you drag Alice to Bob
  • Bob item will move to Alice position and leave an empty space at Bob position for Alice
  • You continue holding and dragging Alice to Charlie, the Charlie moves up and leave an empty space for Alice
  • And if you still drag Alice and jump to Fiona, all of the items prior Fiona and Fiona will move up and leave an empty space for Alice after Fiona

Add the code below to component Home:

1
2 const refDragIndex = useRef<number>()
3
4 const onDragStart = (dragIndex: number) => {
5 refDragIndex.current = dragIndex
6 }
7
8// add index and onDragStart to DragItem
9 {data.map((n, i) => (
10 <DragItem
11 onDragStart={onDragStart}
12 index={i}
13 key={n}
14 name={n}
15 />
16 ))}

And DragItem component

1const DragItem = ({
2 name,
3 index,
4 onDragStart,
5}: {
6 name: string
7 index: number
8 onDragStart: (dragIndex: number) => void
9})
10 ...
11}
12
13// add this after onDragEnd
14onDragStart={() => onDragStart(index)}

Okay, nothing happens on the UI yet because we have just stored the position index of item being dragged. We still have one more function to make a change happen on the UI β€” changing position of dragging item and the target.

1// Add this function to Home component
2const onMove = (dropIndex: number) => {
3 const dragIndex = refDragIndex.current
4
5
6 setData((prevList) => {
7 if (dragIndex !== undefined) {
8 // move dragging item to new position
9 const dragItem = prevList.splice(dragIndex, 1)
10 prevList.splice(dropIndex, 0, ...dragItem)
11 }
12 return [...prevList]
13 })
14
15 // reset drag item position to new index
16 refDragIndex.current = dropIndex
17}
18
19
20// pass onMove to DragItem
21<DragItem
22 onMove={onMove}
23 onDragStart={onDragStart}
24 index={i}
25 key={item}
26 name={item}
27/>

And update the DragItem as below:

1
2// add to props of DragItem component
3 onMove: (dropIndex: number) => void
4
5// add this to DragItem component and
6const _onDragEnter: DragEventHandler<HTMLElement> = (evt) => {
7 evt.preventDefault()
8 onMove(index)
9}
10
11// and defined onDragEnter for the <Box> in DragItem
12onDragEnter={_onDragEnter}
13

And here we are:

Press enter or click to view image in full size

Woohoo!

No more worries about drag-drop list even your work requires to use native api.

Here is the source code for your reference: https://github.com/alanng2050/blog-demo-sortable-drag-drop-list

Good luck!

Thanks for reading!