How to Make a React Component Draggable: Native HTML5 vs. react-draggable

The recommended way to make a React component draggable is to use the native HTML5 draggable attribute combined with synthetic event handlers like onDragStart and onDrop, or leverage the react-draggable library for advanced interactions requiring constrained movement or snapping.

Making elements draggable is a fundamental interaction pattern in modern web applications. In the facebook/react repository, the framework provides first-class support for the HTML5 Drag and Drop API through standard JSX attributes and synthetic events, enabling any component or <div> to become a drag source with minimal configuration.

Using the Native HTML5 draggable Attribute

React treats draggable as a standard DOM property, forwarding it directly to the underlying HTML element. According to the React source code, the property mapping is defined in packages/react-dom-bindings/src/shared/possibleStandardNames.js, where the entry draggable: 'draggable' instructs the reconciler to set this as a DOM attribute.

During the component lifecycle, packages/react-dom/src/client/ReactDOMComponent.js handles the draggable prop in its property-setting switch statement. At line 710, the value is written directly to the DOM node during mounting, while line 2832 contains the corresponding reconciliation logic for updates, ensuring the attribute changes only when the prop value actually differs.

Handling Synthetic Drag Events

When an element has draggable={true} (or simply draggable), React normalizes seven drag-related events through its synthetic event system: onDrag, onDragStart, onDragEnd, onDragEnter, onDragLeave, onDragOver, and onDrop. These work consistently across browsers without requiring manual event listener management.

Here is a complete implementation using the native API:

function DraggableBox() {
  const handleDragStart = (e) => {
    // Set data that will be available to the drop target
    e.dataTransfer.setData('text/plain', 'Dragged box');
    // Optional visual cue
    e.dataTransfer.effectAllowed = 'move';
  };

  const handleDrop = (e) => {
    e.preventDefault(); // required to allow drop
    const data = e.dataTransfer.getData('text/plain');
    console.log('Dropped:', data);
  };

  const handleDragOver = (e) => {
    e.preventDefault(); // enable drop zone
    e.dataTransfer.dropEffect = 'move';
  };

  return (
    <div
      draggable               // native HTML5 draggable attribute
      onDragStart={handleDragStart}
      onDragOver={handleDragOver}
      onDrop={handleDrop}
      style={{
        width: 120,
        height: 80,
        backgroundColor: '#4caf50',
        color: 'white',
        lineHeight: '80px',
        textAlign: 'center',
        cursor: 'grab',
      }}
    >
      Drag me
    </div>
  );
}

The packages/react-dom-bindings/src/client/ReactDOMUnknownPropertyHook.js file ensures that draggable is recognized as a valid DOM property, preventing development warnings, while packages/react-dom/src/__tests__/ReactDOMEventPropagation-test.js contains the test suite verifying that all drag events propagate correctly through the React tree.

Advanced Dragging with react-draggable

For scenarios requiring constrained movement, axis restrictions, or complex positioning logic, the community-maintained react-draggable library provides a declarative wrapper that manages mouse and touch events internally. Unlike the native API, it does not rely on the HTML5 draggable attribute or the dataTransfer API, instead updating the element's transform style directly.

Install the package:

npm install react-draggable

Then implement the component:

import Draggable from 'react-draggable';

function FancyDraggable() {
  return (
    <Draggable
      axis="both"               // x- and y-axis
      bounds="parent"           // confine movement inside parent
      handle=".handle"          // only drag when this child is grabbed
      onStart={() => console.log('drag start')}
      onStop={() => console.log('drag stop')}
    >
      <div
        style={{
          width: 150,
          height: 100,
          background: '#2196f3',
          color: '#fff',
          padding: 10,
          position: 'relative',
        }}
      >
        <div className="handle" style={{ cursor: 'move' }}>
          ⠿ Drag here
        </div>
        Content
      </div>
    </Draggable>
  );
}

This approach works on any element—including custom components—because react-draggable renders a positioning wrapper that intercepts pointer events. You can combine both approaches if you need cross-browser fallback or want to support both drag-and-drop data transfer and visual repositioning.

Summary

  • Native HTML5 API: Use the draggable attribute on any JSX element and handle synthetic events (onDragStart, onDrop, etc.) for standard drag-and-drop data transfer operations.
  • Internal Implementation: React maps draggable through possibleStandardNames.js and applies it in ReactDOMComponent.js during the commit phase.
  • Community Libraries: Use react-draggable for visual repositioning, bounds checking, and interaction patterns that exceed the native API's capabilities.
  • Event Normalization: React's synthetic event system provides consistent dataTransfer handling and event propagation across all browsers.

Frequently Asked Questions

How does React handle the draggable attribute internally?

React treats draggable as a standard DOM property defined in packages/react-dom-bindings/src/shared/possibleStandardNames.js. During the commit phase, packages/react-dom/src/client/ReactDOMComponent.js writes this value directly to the DOM node (line 710) and reconciles changes during updates (line 2832), treating it identically to other HTML attributes like id or class.

What is the difference between native draggable and react-draggable?

The native draggable attribute uses the browser's HTML5 Drag and Drop API, which focuses on data transfer between drag sources and drop targets using dataTransfer. In contrast, react-draggable manages visual positioning through CSS transforms and mouse/touch events, making it suitable for repositioning UI elements within a container but not for cross-window data transfer operations.

Can I make any HTML element draggable in React?

Yes. As implemented in the facebook/react source code, you can add the draggable prop to any JSX element that renders to a standard HTML tag, including <div>, <span>, <img>, and custom components that spread props to a host element. The reconciler validates this through ReactDOMUnknownPropertyHook.js before passing it to the DOM.

Which drag events does React's synthetic event system support?

React supports seven normalized drag events: onDrag, onDragStart, onDragEnd, onDragEnter, onDragLeave, onDragOver, and onDrop. These events provide access to the native dataTransfer object for managing drag data, effect types, and drop zone validation, with event propagation tested in ReactDOMEventPropagation-test.js.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s https://instagit.com/install.md

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client