react-draggable vs react-move vs react-dnd vs react-intersection-observer vs react-spring vs react-swipeable vs react-use vs react-use-gesture
React Interaction and Animation Libraries for Modern Web Applications
react-draggablereact-movereact-dndreact-intersection-observerreact-springreact-swipeablereact-usereact-use-gestureSimilar Packages:

React Interaction and Animation Libraries for Modern Web Applications

These libraries provide specialized capabilities for handling user interactions (drag, drop, swipe, scroll visibility) and declarative animations in React applications. react-dnd and react-draggable focus on drag-and-drop functionality with different abstraction levels. react-intersection-observer enables efficient viewport visibility detection using the native Intersection Observer API. react-move, react-spring, and parts of react-use offer animation solutions with varying complexity and performance characteristics. react-swipeable handles touch-based swipe gestures, while react-use-gesture and react-use provide reusable hooks for common interaction patterns and utility functions.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
react-draggable4,883,3609,294243 kB2138 months agoMIT
react-move84,3396,580-285 years agoMIT
react-dnd021,634231 kB474-MIT
react-intersection-observer05,533164 kB220 days agoMIT
react-spring029,0548.36 kB1346 months agoMIT
react-swipeable02,11387.5 kB31a year agoMIT
react-use043,950454 kB653a year agoUnlicense
react-use-gesture09,582-505 years agoMIT

React Interaction and Animation Libraries: A Practical Guide for Frontend Architects

When building modern React applications, you'll inevitably face decisions about how to handle user interactions and animations. The ecosystem offers several specialized libraries, each solving different problems with distinct trade-offs. Let's break down when to use which tool β€” and when to avoid them entirely.

⚠️ Deprecation Alert: react-move Is Retired

Before diving into comparisons, note that react-move is officially deprecated. Its npm page states: "This project is no longer maintained." Do not use it in new projects. For similar animation capabilities, consider react-spring or framer-motion instead.

πŸ–±οΈ Drag-and-Drop: Full Framework vs Lightweight Utility

react-dnd: The Enterprise-Grade DnD System

react-dnd provides a complete drag-and-drop framework with backends for HTML5, touch devices, and test environments. It uses a higher-order component pattern and context to manage drag state across components.

// react-dnd example
import { useDrag, useDrop } from 'react-dnd';

const Item = ({ id, text }) => {
  const [{ isDragging }, drag] = useDrag(() => ({
    type: 'item',
    item: { id },
    collect: (monitor) => ({
      isDragging: !!monitor.isDragging(),
    }),
  }));

  return (
    <div ref={drag} style={{ opacity: isDragging ? 0.5 : 1 }}>
      {text}
    </div>
  );
};

const DropZone = () => {
  const [{ isOver }, drop] = useDrop(() => ({
    accept: 'item',
    drop: (item) => console.log('Dropped:', item),
    collect: (monitor) => ({
      isOver: !!monitor.isOver(),
    }),
  }));

  return <div ref={drop} style={{ background: isOver ? '#f0f0f0' : '#fff' }}>Drop here</div>;
};

react-draggable: Simple Element Dragging

react-draggable focuses solely on making individual elements draggable without complex drop logic. It's much lighter but lacks built-in drop zone management.

// react-draggable example
import Draggable from 'react-draggable';

const DraggableBox = () => (
  <Draggable
    axis="both"
    handle=".handle"
    defaultPosition={{ x: 0, y: 0 }}
    position={null}
    grid={[10, 10]}
  >
    <div>
      <div className="handle" style={{ cursor: 'move' }}>Drag me</div>
      <div>Content</div>
    </div>
  </Draggable>
);

When to choose which?

  • Need complex drop zones, accessibility, and production-grade DnD? β†’ react-dnd
  • Just need to drag a single element around? β†’ react-draggable

πŸ‘οΈ Visibility Detection: The Right Way to Lazy Load

react-intersection-observer: Native API, Reactified

This library wraps the browser's Intersection Observer API in a clean hook interface, avoiding scroll event listeners that hurt performance.

// react-intersection-observer example
import { useInView } from 'react-intersection-observer';

const LazyImage = ({ src }) => {
  const { ref, inView } = useInView({
    triggerOnce: true,
    threshold: 0.1,
  });

  return (
    <div ref={ref}>
      {inView ? <img src={src} alt="" /> : <div>Loading...</div>}
    </div>
  );
};

Unlike manual scroll handlers, this approach is efficient because the browser only notifies you when elements actually cross the viewport boundary.

πŸŒ€ Animation Approaches: Physics vs Keyframes

react-spring: Spring-Physics Animations

react-spring uses spring physics instead of duration-based animations, creating more natural motion. It updates values on every frame without re-rendering the entire component tree.

// react-spring example
import { useSpring, animated } from 'react-spring';

const AnimatedBox = ({ isVisible }) => {
  const props = useSpring({
    opacity: isVisible ? 1 : 0,
    transform: isVisible ? 'translateY(0px)' : 'translateY(-20px)',
    config: { tension: 200, friction: 20 },
  });

  return <animated.div style={props}>Hello</animated.div>;
};

react-use Animation Hooks (Limited)

While react-use includes some animation-related hooks like useRaf, it doesn't provide a dedicated animation system. Its useTimeout and useInterval can coordinate timing but lack interpolation and physics.

// react-use timing example
import { useBoolean, useTimeout } from 'react-use';

const TimedMessage = () => {
  const [show, setShow] = useBoolean(true);
  
  useTimeout(() => {
    setShow(false);
  }, 3000);

  return show ? <div>Disappears after 3s</div> : null;
};

Key difference: react-spring handles the animation math and rendering optimization, while react-use provides timing utilities you'd combine with other animation approaches.

βœ‹ Gesture Handling: Unified vs Specialized

react-use-gesture: One Hook to Rule Them All

This library provides a single useGesture hook that handles drag, pinch, wheel, scroll, and move gestures with normalized data across devices.

// react-use-gesture example
import { useGesture } from 'react-use-gesture';
import { useSpring, animated } from 'react-spring';

const DraggableCard = () => {
  const [{ x, y }, api] = useSpring(() => ({ x: 0, y: 0 }));
  
  const bind = useGesture({
    onDrag: ({ offset: [x, y] }) => api.start({ x, y }),
    onPinch: ({ offset: [s] }) => api.start({ scale: s / 100 + 1 }),
  });

  return (
    <animated.div
      {...bind()}
      style={{ x, y, scale: api.get().scale || 1 }}
    >
      Drag or pinch me
    </animated.div>
  );
};

react-swipeable: Swipe-Specific Simplicity

If you only need swipe detection (common for mobile UIs), react-swipeable provides a focused API without the overhead of supporting other gestures.

// react-swipeable example
import { useSwipeable } from 'react-swipeable';

const SwipeCarousel = () => {
  const handlers = useSwipeable({
    onSwipedLeft: () => console.log('Next slide'),
    onSwipedRight: () => console.log('Previous slide'),
    delta: 10, // min distance(px) before a swipe starts
  });

  return <div {...handlers}>Swipe me</div>;
};

react-use Gesture Hooks (Basic)

react-use includes basic hooks like useDrag but they're less feature-rich than dedicated gesture libraries:

// react-use drag example
import { useDrag } from 'react-use';

const SimpleDrag = () => {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const bind = useDrag(({ xy: [x, y] }) => setPosition({ x, y }));

  return <div {...bind()} style={{ left: position.x, top: position.y }}>Drag</div>;
};

When to choose which?

  • Need multiple coordinated gestures? β†’ react-use-gesture
  • Only swipes on touch devices? β†’ react-swipeable
  • Simple drag with other utilities needed? β†’ react-use

🧰 Utility Collections vs Focused Tools

react-use: The Swiss Army Knife

react-use isn't a single-purpose library but a collection of 80+ hooks covering everything from browser APIs (useLocalStorage, useMedia) to performance (useDebounce, useThrottle) and state management (useSet, useMap).

// react-use utility example
import { useLocalStorage, useDebounce } from 'react-use';

const SearchBox = () => {
  const [query, setQuery] = useLocalStorage('search', '');
  const debouncedQuery = useDebounce(query, 300);

  useEffect(() => {
    if (debouncedQuery) fetchResults(debouncedQuery);
  }, [debouncedQuery]);

  return <input value={query} onChange={(e) => setQuery(e.target.value)} />;
};

Use it when you need multiple utility hooks, but avoid it if you only need one specific interaction pattern β€” the bundle size includes all hooks even if you use just one.

πŸ“Š Decision Matrix

ScenarioBest ChoiceWhy
Complex drag-and-drop with accessibilityreact-dndProduction-ready, supports multiple backends, handles edge cases
Simple draggable elementreact-draggableLightweight, zero-config for basic dragging
Lazy loading images/contentreact-intersection-observerEfficient, native API wrapper, no scroll jank
Physics-based animationsreact-spring60fps performance, spring physics, React-optimized
Mobile swipe gesturesreact-swipeableFocused API, touch-optimized, minimal overhead
Multi-touch gestures (pinch/drag)react-use-gestureUnified API, normalized data, performant
General utility hooksreact-useComprehensive collection, well-tested, covers many use cases
Legacy animation needsAvoid react-moveDeprecated, unmaintained, security risks

πŸ’‘ Final Architecture Advice

  1. Avoid over-engineering: If you only need to detect viewport visibility, don't pull in a full gesture library. Use react-intersection-observer.

  2. Bundle size matters: react-use is convenient but includes many hooks you might not need. Consider copying individual hooks if you only use one or two.

  3. Performance first: For animations, prefer react-spring over CSS transitions triggered by state changes β€” it avoids layout thrashing and maintains 60fps.

  4. Mobile considerations: On touch devices, react-use-gesture or react-swipeable handle touch event normalization better than raw event listeners.

  5. Deprecation discipline: Never use deprecated libraries like react-move in new projects. The maintenance cost always outweighs short-term convenience.

The right choice depends entirely on your specific interaction requirements, performance constraints, and maintenance strategy. When in doubt, start with the most focused solution that meets your immediate needs β€” you can always expand later.

How to Choose: react-draggable vs react-move vs react-dnd vs react-intersection-observer vs react-spring vs react-swipeable vs react-use vs react-use-gesture

  • react-draggable:

    Choose react-draggable when you need straightforward, lightweight dragging of individual elements without complex drop logic. It's perfect for implementing draggable modals, resizable panels, or simple UI elements that follow the mouse/touch. Avoid it if you need sophisticated drop zone interactions or accessibility features out of the box, as it focuses purely on the dragging mechanics.

  • react-move:

    Choose react-move only for maintaining legacy applications, as it has been deprecated according to its npm page. The project is no longer actively maintained and should not be used in new projects. Consider modern alternatives like react-spring or framer-motion for similar animation capabilities with active support and better performance.

  • react-dnd:

    Choose react-dnd when you need a full-featured, production-ready drag-and-drop system that supports complex scenarios like nested drop zones, custom drag previews, and multiple backend implementations (HTML5, touch, etc.). It's ideal for applications like Trello-style boards or file explorers where drag operations must be highly customizable and accessible. However, its higher abstraction layer comes with more boilerplate compared to simpler alternatives.

  • react-intersection-observer:

    Choose react-intersection-observer when you need to detect when elements enter or leave the viewport for lazy loading, infinite scrolling, or triggering animations. It provides a clean React hook interface to the native Intersection Observer API with minimal overhead. This is the go-to solution for performance-sensitive visibility detection without manual scroll event handling.

  • react-spring:

    Choose react-spring when you need high-performance, physics-based animations that integrate smoothly with React's rendering model. It excels at creating fluid transitions for lists, routes, and complex UI elements using springs instead of duration-based timing. Its hooks-based API (useSpring, useTransition) provides fine-grained control while maintaining 60fps performance through direct manipulation of the render tree.

  • react-swipeable:

    Choose react-swipeable when your application requires reliable touch-based swipe gesture detection on mobile devices or touchscreens. It handles the complexities of touch event normalization across browsers and provides configurable thresholds for swipe direction and distance. Ideal for image carousels, mobile navigation drawers, or any UI that responds to horizontal/vertical swipes.

  • react-use:

    Choose react-use when you need a comprehensive collection of battle-tested React hooks for common tasks beyond just gestures or animations. While it includes some interaction hooks (like useDrag), its strength lies in providing utilities for state management, side effects, browser APIs, and performance optimizations. Use it as a utility belt rather than a focused solution for specific interaction patterns.

  • react-use-gesture:

    Choose react-use-gesture when you need a unified, performant API for handling multiple gesture types (drag, pinch, wheel, scroll, move) with shared configuration and state. It abstracts away browser inconsistencies and provides normalized gesture data through a single useGesture hook. Perfect for building interactive visualizations, custom sliders, or any UI requiring coordinated multi-touch interactions.

README for react-draggable

React-Draggable

TravisCI Build Status Appveyor Build Status npm downloads gzip size version

A simple component for making elements draggable.

<Draggable>
  <div>I can now be moved around!</div>
</Draggable>
VersionCompatibility
4.xReact 16.3+
3.xReact 15-16
2.xReact 0.14 - 15
1.xReact 0.13 - 0.14
0.xReact 0.10 - 0.13

Technical Documentation

Installing

$ npm install react-draggable

If you aren't using browserify/webpack, a UMD version of react-draggable is available. It is updated per-release only. This bundle is also what is loaded when installing from npm. It expects external React and ReactDOM.

If you want a UMD version of the latest master revision, you can generate it yourself from master by cloning this repository and running $ make. This will create umd dist files in the dist/ folder.

Exports

The default export is <Draggable>. At the .DraggableCore property is <DraggableCore>. Here's how to use it:

// ES6
import Draggable from 'react-draggable'; // The default
import {DraggableCore} from 'react-draggable'; // <DraggableCore>
import Draggable, {DraggableCore} from 'react-draggable'; // Both at the same time

// CommonJS
let Draggable = require('react-draggable');
let DraggableCore = Draggable.DraggableCore;

<Draggable>

A <Draggable> element wraps an existing element and extends it with new event handlers and styles. It does not create a wrapper element in the DOM.

Draggable items are moved using CSS Transforms. This allows items to be dragged regardless of their current positioning (relative, absolute, or static). Elements can also be moved between drags without incident.

If the item you are dragging already has a CSS Transform applied, it will be overwritten by <Draggable>. Use an intermediate wrapper (<Draggable><span>...</span></Draggable>) in this case.

Draggable Usage

View the Demo and its source for more.

import React from 'react';
import ReactDOM from 'react-dom';
import Draggable from 'react-draggable';

class App extends React.Component {

  eventLogger = (e: MouseEvent, data: Object) => {
    console.log('Event: ', e);
    console.log('Data: ', data);
  };

  render() {
    return (
      <Draggable
        axis="x"
        handle=".handle"
        defaultPosition={{x: 0, y: 0}}
        position={null}
        grid={[25, 25]}
        scale={1}
        onStart={this.handleStart}
        onDrag={this.handleDrag}
        onStop={this.handleStop}>
        <div>
          <div className="handle">Drag from here</div>
          <div>This readme is really dragging on...</div>
        </div>
      </Draggable>
    );
  }
}

ReactDOM.render(<App/>, document.body);

Draggable API

The <Draggable/> component transparently adds draggability to its children.

Note: Only a single child is allowed or an Error will be thrown.

For the <Draggable/> component to correctly attach itself to its child, the child element must provide support for the following props:

  • style is used to give the transform css to the child.
  • className is used to apply the proper classes to the object being dragged.
  • onMouseDown, onMouseUp, onTouchStart, and onTouchEnd are used to keep track of dragging state.

React.DOM elements support the above properties by default, so you may use those elements as children without any changes. If you wish to use a React component you created, you'll need to be sure to transfer prop.

<Draggable> Props:

//
// Types:
//
type DraggableEventHandler = (e: Event, data: DraggableData) => void | false;
type DraggableData = {
  node: HTMLElement,
  // lastX + deltaX === x
  x: number, y: number,
  deltaX: number, deltaY: number,
  lastX: number, lastY: number
};

//
// Props:
//
{
// If set to `true`, will allow dragging on non left-button clicks.
allowAnyClick: boolean,

// Default `false` and default behavior before 4.5.0.
// If set to `true`, the 'touchstart' event will not be prevented,
// which will allow scrolling inside containers. We recommend
// using the 'handle' / 'cancel' props when possible instead of enabling this.
// 
// See https://github.com/react-grid-layout/react-draggable/issues/728
allowMobileScroll: boolean,

// Determines which axis the draggable can move. This only affects
// flushing to the DOM. Callbacks will still include all values.
// Accepted values:
// - `both` allows movement horizontally and vertically (default).
// - `x` limits movement to horizontal axis.
// - `y` limits movement to vertical axis.
// - 'none' stops all movement.
axis: string,

// Specifies movement boundaries. Accepted values:
// - `parent` restricts movement within the node's offsetParent
//    (nearest node with position relative or absolute), or
// - a selector, restricts movement within the targeted node
// - An object with `left, top, right, and bottom` properties.
//   These indicate how far in each direction the draggable
//   can be moved.
bounds: {left?: number, top?: number, right?: number, bottom?: number} | string,

// Specifies a selector to be used to prevent drag initialization. The string is passed to
// Element.matches, so it's possible to use multiple selectors like `.first, .second`.
// Example: '.body'
cancel: string,

// Class names for draggable UI.
// Default to 'react-draggable', 'react-draggable-dragging', and 'react-draggable-dragged'
defaultClassName: string,
defaultClassNameDragging: string,
defaultClassNameDragged: string,

// Specifies the `x` and `y` that the dragged item should start at.
// This is generally not necessary to use (you can use absolute or relative
// positioning of the child directly), but can be helpful for uniformity in
// your callbacks and with css transforms.
defaultPosition: {x: number, y: number},

// If true, will not call any drag handlers.
disabled: boolean,

// Default `true`. Adds "user-select: none" while dragging to avoid selecting text.
enableUserSelectHack: boolean,

// Specifies the x and y that dragging should snap to.
grid: [number, number],

// Specifies a selector to be used as the handle that initiates drag.
// Example: '.handle'
handle: string,

// If desired, you can provide your own offsetParent for drag calculations.
// By default, we use the Draggable's offsetParent. This can be useful for elements
// with odd display types or floats.
offsetParent: HTMLElement,

// Called whenever the user mouses down. Called regardless of handle or
// disabled status.
onMouseDown: (e: MouseEvent) => void,

// Called when dragging starts. If `false` is returned any handler,
// the action will cancel.
onStart: DraggableEventHandler,

// Called while dragging.
onDrag: DraggableEventHandler,

// Called when dragging stops.
onStop: DraggableEventHandler,

// If running in React Strict mode, ReactDOM.findDOMNode() is deprecated.
// Unfortunately, in order for <Draggable> to work properly, we need raw access
// to the underlying DOM node. If you want to avoid the warning, pass a `nodeRef`
// as in this example:
//
// function MyComponent() {
//   const nodeRef = React.useRef(null);
//   return (
//     <Draggable nodeRef={nodeRef}>
//       <div ref={nodeRef}>Example Target</div>
//     </Draggable>
//   );
// }
//
// This can be used for arbitrarily nested components, so long as the ref ends up
// pointing to the actual child DOM node and not a custom component.
//
// For rich components, you need to both forward the ref *and props* to the underlying DOM
// element. Props must be forwarded so that DOM event handlers can be attached. 
// For example:
//
//   const Component1 = React.forwardRef(function (props, ref) {
//     return <div {...props} ref={ref}>Nested component</div>;
//   });
//
//   const nodeRef = React.useRef(null);
//   <DraggableCore onDrag={onDrag} nodeRef={nodeRef}>
//     <Component1 ref={nodeRef} />
//   </DraggableCore>
//
// Thanks to react-transition-group for the inspiration.
//
// `nodeRef` is also available on <DraggableCore>.
nodeRef: React.Ref<typeof React.Component>,

// Much like React form elements, if this property is present, the item
// becomes 'controlled' and is not responsive to user input. Use `position`
// if you need to have direct control of the element.
position: {x: number, y: number}

// A position offset to start with. Useful for giving an initial position
// to the element. Differs from `defaultPosition` in that it does not
// affect the position returned in draggable callbacks, and in that it
// accepts strings, like `{x: '10%', y: '10%'}`.
positionOffset: {x: number | string, y: number | string},

// Specifies the scale of the canvas your are dragging this element on. This allows
// you to, for example, get the correct drag deltas while you are zoomed in or out via
// a transform or matrix in the parent of this element.
scale: number
}

Note that sending className, style, or transform as properties will error - set them on the child element directly.

Controlled vs. Uncontrolled

<Draggable> is a 'batteries-included' component that manages its own state. If you want to completely control the lifecycle of the component, use <DraggableCore>.

For some users, they may want the nice state management that <Draggable> provides, but occasionally want to programmatically reposition their components. <Draggable> allows this customization via a system that is similar to how React handles form components.

If the prop position: {x: number, y: number} is defined, the <Draggable> will ignore its internal state and use the provided position instead. Alternatively, you can seed the position using defaultPosition. Technically, since <Draggable> works only on position deltas, you could also seed the initial position using CSS top/left.

We make one modification to the React philosophy here - we still allow dragging while a component is controlled. We then expect you to use at least an onDrag or onStop handler to synchronize state.

To disable dragging while controlled, send the prop disabled={true} - at this point the <Draggable> will operate like a completely static component.

<DraggableCore>

For users that require absolute control, a <DraggableCore> element is available. This is useful as an abstraction over touch and mouse events, but with full control. <DraggableCore> has no internal state.

See React-Resizable and React-Grid-Layout for some usage examples.

<DraggableCore> is a useful building block for other libraries that simply want to abstract browser-specific quirks and receive callbacks when a user attempts to move an element. It does not set styles or transforms on itself and thus must have callbacks attached to be useful.

DraggableCore API

<DraggableCore> takes a limited subset of options:

{
  allowAnyClick: boolean,
  allowMobileScroll: boolean,
  cancel: string,
  disabled: boolean,
  enableUserSelectHack: boolean,
  offsetParent: HTMLElement,
  grid: [number, number],
  handle: string,
  onStart: DraggableEventHandler,
  onDrag: DraggableEventHandler,
  onStop: DraggableEventHandler,
  onMouseDown: (e: MouseEvent) => void,
  scale: number
}

Note that there is no start position. <DraggableCore> simply calls drag handlers with the below parameters, indicating its position (as inferred from the underlying MouseEvent) and deltas. It is up to the parent to set actual positions on <DraggableCore>.

Drag callbacks (onStart, onDrag, onStop) are called with the same arguments as <Draggable>.


Contributing

  • Fork the project
  • Run the project in development mode: $ npm run dev
  • Make changes.
  • Add appropriate tests
  • $ npm test
  • If tests don't pass, make them pass.
  • Update README with appropriate docs.
  • Commit and PR

Release checklist

  • Update CHANGELOG
  • make release-patch, make release-minor, or make-release-major
  • make publish

License

MIT