Simple and straightforward to use, the React-Draggable library applies CSS transformations to React components, allowing you to drag components across a UI. With different props that let you change components’ behavior, React-Draggable is a great choice for creating intuitive, user-friendly interfaces.
In this tutorial, we’ll explore React-Draggable in depth by creating a task list with draggable components. Let’s get started!
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
Before installing React-Draggable, make sure you have a React project set up on your local machine. Navigate to the project and run the following command in the terminal:
npm install react-draggable
Now, we’ll import the <Draggable/> component from the library. To add movement to an element or a component, we can simply wrap the <Draggable/> component around it.
Add the following code to App.js:
import Draggable from "react-draggable";
function App() {
return (
<Draggable>
<div>I can now be moved around!</div>
</Draggable>
);
}
export default App;
The code above will render the following output in the browser:

Now that you know how the React-Draggable library works, let’s get started on our task list.
With the React-Draggable library installed, we can set up the required file structure for our project and review the components that we’ll need. To assign unique keys to key components, we’ll need to install a UUID() as follows:
npm i uuid
Our folder structure will look like the image below:

We’ve broken down the logic for our project into three different components. Our task list will use three columns to render their respective tasks.
We’ll add a form element to take input from the user and add the task in the respective component. Our App.js file handles the main task list in its state:
import "./App.css";
import { useState } from "react";
function App() {
const [tasks, setTasks] = useState([]);
const [addItem, setAddItem] = useState(false);
const handleSubmit = () => {
setAddItem(!addItem);
};
const addTask = (task) => {
setTasks(task);
};
return (
<div>
</div>
);
}
export default App;
In the code above, we’ve added the following functions:
tasks: holds the array of tasks that will be rendered on the screenaddItem: the state for showing and hiding the AddForm element, which adds a taskNow that we have the states ready, let’s create the other components.
Add the following code to Header.js:
import React from "react";
export default function Header({ handleSubmit }) {
return (
<div className='header'>
<button onClick={handleSubmit} className='addButton'>
Add Task
</button>
<p>Planning</p>
<p>In Progress</p>
<p>Done</p>
</div>
);
}
The </button> gets the handleSubmit function as a prop, which will show the AddForm element. With our header set up, let’s create the AddForm element:
AddForm elementAdd the following code to AddTaskForm.js:
import React, { useState } from "react";
import { v4 as uuidv4 } from "uuid";
export default function AddTaskForm({ setAddItem, addItem, tasks, setTasks }) {
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [option, setOption] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
let newTask = {
id: uuidv4(),
name: name,
description: description,
timeline: option,
};
setTasks([...tasks, newTask]);
setAddItem(!addItem);
};
return (
<div className='addForm'>
<form onSubmit={(e) => handleSubmit(e)}>
<input
type='text'
placeholder='Name'
onChange={(e) => setName(e.target.value)}
/>
<input
type='text'
placeholder='Description'
onChange={(e) => setDescription(e.target.value)}
/>
<select
name='timeline'
id='timeline'
onChange={(e) => {
setOption(e.target.value);
}}
>
<option value=''></option>
<option value='planning'>Planning</option>
<option value='inprogress'>In-Progress</option>
<option value='done'>Done</option>
</select>
<button type='submit'>Add</button>
</form>
</div>
);
}
In the code block above, we have input fields that will take relevant data provided by users. Each task has an id, name, date, and timeline , which are taken by the select element.
When the form is submitted, each task will be added to the task list that we created in App.js. When the form is submitted successfully, it will be hidden.
In our task list, we’ll have three columns titled Planning, In Progress, and Done. Let’s create these components and add their respective code:
Planning.jsimport React from "react";
import Card from "./Card";
export default function Planning({ tasks, addTask }) {
return (
<>
{tasks
.filter((item) => item.timeline === "planning")
.map((e) => (
<Card currentTask={e} tasks={tasks} addTask={addTask} />
))}
</>
);
}
InProgress.jsimport React from "react";
import Card from "./Card";
export default function InProgress({ tasks, addTask }) {
return (
<>
{tasks
.filter((item) => item.timeline === "inprogress")
.map((e) => (
<Card currentTask={e} tasks={tasks} addTask={addTask} />
))}
</>
);
}
Done.jsimport React from "react";
import Card from "./Card";
export default function Done({ tasks, addTask }) {
return (
<>
{tasks
.filter((item) => item.timeline === "done")
.map((e) => (
<Card currentTask={e} tasks={tasks} addTask={addTask} />
))}
</>
);
}
You may have noticed that all of these components hold a similar logic. We are getting the tasks and filtering them based on their timeline using tasks.filter(), which removes the task objects that do not belong in the column.
For example, in the planning component, filter() will remove every object that does not have the planning string in its timeline attribute.
The array returns a result and renders a <Card/> component. Let’s create Card.js in the same directory and add the code snippet below:
import React from "react";
import Draggable from "react-draggable";
export default function Card({ currentTask, tasks, addTask }) {
return (
<Draggable grid={[10, 10]} axis='y' bounds='parent'>
<div className='card' key={currentTask.id}>
<div className='heading'>
<h3>{currentTask.name && currentTask.name}</h3>
<img
onClick={() => {
const newTaskList = tasks.filter((item) => {
if (item.id != currentTask.id) {
return item;
}
});
addTask(newTaskList);
}}
src='https://toppng.com/uploads/preview/recycling-bin-vector-delete-icon-png-black-11563002079w1isxqyyiv.png'
style={{ height: "20px", width: "20px" }}
/>
</div>
<p>{currentTask.description}</p>
</div>
</Draggable>
);
}
The <Card/> component gets three props, which are required for rendering and deleting logic.
The parent div <Draggable> is imported from our library, making the whole card moveable. By default, you can move the card anywhere on the screen. However, to prevent it from going outside of its parent element, we’ll provide a value to a bounds prop.
For removing a task, add the code below:
const newTaskList = tasks.filter((item) => {
if (item.id != currentTask.id) {
return item;
}
});
addTask(newTaskList);
The code block above gets the entire list of tasks from App.js, then creates a new list that excludes the current task. Next, let’s import each component in App.js:
import "./App.css";
import { useState } from "react";
import Header from "./Header";
import Planning from "./Planning";
import InProgress from "./InProgress";
import Done from "./Done";
import AddTaskForm from "./AddTaskForm";
function App() {
const [tasks, setTasks] = useState([]);
const [addItem, setAddItem] = useState(false);
const handleSubmit = () => {
setAddItem(!addItem);
};
const addTask = (task) => {
setTasks(task);
};
return (
<div>
<Header handleSubmit={handleSubmit} />
<div className='mainGrid'>
<div className='column'>
<Planning tasks={tasks} addTask={addTask} />
</div>
<div className='column'>
<InProgress tasks={tasks} addTask={addTask} />
</div>
<div className='column'>
<Done tasks={tasks} addTask={addTask} />
</div>
{addItem && (
<AddTaskForm
addItem={addItem}
setAddItem={setAddItem}
tasks={tasks}
setTasks={addTask}
/>
)}
</div>
</div>
);
}
export default App;
Finally, we’ll add styles to our task list. Add the following code in App.css:
.mainGrid{
display: flex;
flex-direction: row;
justify-content: space-between;
flex-wrap: wrap;
height: 100vh;
}
.header{
display: flex;
flex-direction: row;
justify-content: space-around;
flex-wrap: wrap;
background-color: rgb(205, 240, 240)
}
.column{
width: 32vw;
display: flex;
flex-direction: column;
border: 5px solid grey;
background-color: azure;
overflow: hidden;
}
.card{
border: 1px solid black;
background-color: white;
}
.card p{
font-size: 10px;
width: 30vw;
}
.heading{
display: flex;
flex-direction:row;
align-items: center;
justify-content: space-between;
}
.addButton{
position:absolute;
top: 2.5%;
left: 0%;
}
.addForm{
height: 50vh;
width: 50vw;
background-color: lightblue;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
position: absolute;
top: 25%;
left: 25%;
}
.addForm form{
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
When we run the project and add an item to our task list, the output will look like the image below:

These tasks are moveable within their respective columns:

To delete a task, simply click on the delete icon.
The react-draggable API supports a list of callback methods to track the movement of the draggable element:
onDrag() — This method listens to the drag event and gets triggered when drag is in processonStart() — This method listens to the drag event and gets triggered when dragging startsonStop() — This method listens to the drag event and gets triggered when dragging stopsonMouseDown() — This method gets triggered when the mouse is pressed on a draggable elementonMouseUp() — This method gets triggered when the mouse is released on a draggable elementonTouchStart() — This method gets triggered in the touch environment before drag startonTouchEnd() — This method gets triggered in the touch environment before drag stopsimport Draggable, {DraggableCore} from 'react-draggable';
import React from 'react-dom'
function MyDraggableComponent() {
const eventHandler = (e, data) => {
console.log('Event Type', e.type);
console.log({e, data});
}
return (
<Draggable
defaultPosition={{x: 0, y: 0}}
onMouseDown={eventHandler}
onStart={eventHandler}
onDrag={eventHandler}
onStop={eventHandler}>
<div style={{border: "2px solid red", padding: "1rem", width: "30%"}}>
<div className="handle">Drag from here</div>
<div>This readme is really dragging on...</div>
</div>
</Draggable>
)
}
export default MyDraggableComponent;
The handler function receives the event and data objects as parameters.
Additionally, the data object contains information about the current position of the draggable component.
axisThe axis prop determines on which axis the draggable can move. It receives the following string values:
both allows movement horizontally and vertically. This is the default value for the axisx limits movement to the horizontal axisy limits movement to the vertical axisnone stops all movement<Draggable axis="x">
<div style={{border: "2px solid red", padding: "1rem", width: "30%"}}>
<div className="handle">Drag from here</div>
<div>This readme is really dragging on...</div>
</div>
</Draggable>
handleThe handle prop defines a selector that will be used as the drag handle.
Without the handle prop, the draggable component will be used as the drag handle:
<Draggable handle=".handle">
<div style={{border: "2px solid red", padding: "1rem", width: "30%"}}>
<div style={{backgroundColor: "green", width: "30%"}} className="handle">
Drag from here
</div>
<div>This readme is really dragging on...</div>
</div>
</Draggable>
With the above snippet, we can move the draggable component by clicking on the green background color element.
defaultPositionThe defaultPosition prop specifies the x and y coordinate that the dragged item should start:
<Draggable defaultPosition={{x: 0, y: 0}}>
<div style={{border: "2px solid red", padding: "1rem", width: "30%"}}>
<div className="handle">Drag from here</div>
<div>This readme is really dragging on...</div>
</div>
</Draggable>
disabledThe disabled prop receives a Boolean value. If true, it will not call any drag handlers, making the draggable component operate like a completely static component.
boundsThe bounds prop specifies the movement boundaries. It receives the following string values:
parent — The parent value restricts movement within the node’s offsetParent (the nearest node with position relative or absolute)left, top, right, and bottom properties, which indicate how far in each direction the draggable can be moved<Draggable bounds={{left: 0, top: 0, right: 100, bottom: 200}}>
<div style={{border: "2px solid red", padding: "1rem", width: "30%"}}>
<div className="handle">Drag from here</div>
<div>This readme is really dragging on...</div>
</div>
</Draggable>
With the above snippet, the movement of our draggable component will be restricted to the specified values.
For more information on draggable props, check the documentation.
findDOMNode deprecation errorIf the draggable component is running in React Strict mode, the warning ReactDOM.findDOMNode() is deprecated will be logged on the browser’s console.
The fix to avoid this warning is to pass a nodeRef as in this example:
function MyDraggableComponent() {
const nodeRef = React.useRef(null);
return (
<Draggable nodeRef={nodeRef}>
<div ref={nodeRef}>Example Target</div>
</Draggable>
);
}
export default MyDraggableComponent;
Adding movement to components in your application has benefits for your UI like improved visual interest and better organization. The React-Draggable library is a great choice for its simplicity and functionality.
In this tutorial, we used React-Draggable to create a task list. To learn more about React-Draggable, be sure to visit the GitHub page.
Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, not
server-side
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
// Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>

Examine AgentKit, Open AI’s new tool for building agents. Conduct a side-by-side comparison with n8n by building AI agents with each tool.

AI agents powered by MCP are redefining interfaces, shifting from clicks to intelligent, context-aware conversations.

Learn how platform engineering helps frontend teams streamline workflows with Backstage, automating builds, documentation, and project management.

Build an AI assistant with Vercel AI Elements, which provides pre-built React components specifically designed for AI applications.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now