Convert Figma logo to code with AI

tajo logoreact-movable

🔀 Drag and drop for your React lists and tables. Accessible. Tiny.

1,562
52
1,562
9

Top Related Projects

Beautiful and accessible drag and drop for lists with React

A set of higher-order components to turn any list into an animated, accessible and touch-friendly sortable list✌️

React bindings for SortableJS

Drag-and-drop sortable component for nested data and hierarchies

20,973

Drag and Drop for React

🖱 A resizable and draggable component for React.

Quick Overview

React-movable is a lightweight and flexible library for creating drag and drop lists in React applications. It provides a simple API to implement sortable lists with smooth animations, supporting both mouse and touch interactions.

Pros

  • Easy to integrate with existing React projects
  • Supports both horizontal and vertical list orientations
  • Customizable drag handle and item appearance
  • Lightweight with minimal dependencies

Cons

  • Limited documentation and examples
  • May require additional styling for complex layouts
  • Not as feature-rich as some alternative drag and drop libraries
  • Limited support for nested lists or grid layouts

Code Examples

Basic sortable list:

import { List, arrayMove } from 'react-movable';

function SortableList() {
  const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);

  return (
    <List
      values={items}
      onChange={({ oldIndex, newIndex }) =>
        setItems(arrayMove(items, oldIndex, newIndex))
      }
      renderList={({ children, props }) => <ul {...props}>{children}</ul>}
      renderItem={({ value, props }) => <li {...props}>{value}</li>}
    />
  );
}

Custom drag handle:

import { List, arrayMove } from 'react-movable';

function CustomHandleList() {
  const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);

  return (
    <List
      values={items}
      onChange={({ oldIndex, newIndex }) =>
        setItems(arrayMove(items, oldIndex, newIndex))
      }
      renderList={({ children, props }) => <ul {...props}>{children}</ul>}
      renderItem={({ value, props }) => (
        <li {...props}>
          <span className="drag-handle"></span>
          {value}
        </li>
      )}
      dragHandle=".drag-handle"
    />
  );
}

Horizontal list:

import { List, arrayMove } from 'react-movable';

function HorizontalList() {
  const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);

  return (
    <List
      values={items}
      onChange={({ oldIndex, newIndex }) =>
        setItems(arrayMove(items, oldIndex, newIndex))
      }
      renderList={({ children, props }) => (
        <div {...props} style={{ display: 'flex' }}>
          {children}
        </div>
      )}
      renderItem={({ value, props }) => (
        <div {...props} style={{ margin: '0 10px' }}>
          {value}
        </div>
      )}
      direction="horizontal"
    />
  );
}

Getting Started

  1. Install the package:

    npm install react-movable
    
  2. Import the necessary components:

    import { List, arrayMove } from 'react-movable';
    
  3. Create a state for your list items:

    const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);
    
  4. Implement the List component with the required props:

    <List
      values={items}
      onChange={({ oldIndex, newIndex }) =>
        setItems(arrayMove(items, oldIndex, newIndex))
      }
      renderList={({ children, props }) => <ul {...props}>{children}</ul>}
      renderItem={({ value, props }) => <li {...props}>{value}</li>}
    />
    

Competitor Comparisons

Beautiful and accessible drag and drop for lists with React

Pros of react-beautiful-dnd

  • More feature-rich, offering advanced capabilities like multi-drag and combining items
  • Extensive documentation and examples, making it easier for developers to implement and customize
  • Larger community and wider adoption, leading to better support and more frequent updates

Cons of react-beautiful-dnd

  • Larger bundle size, which may impact performance in smaller projects
  • Steeper learning curve due to its more complex API and configuration options
  • Less suitable for simple drag-and-drop scenarios where a lightweight solution would suffice

Code Comparison

react-beautiful-dnd:

<DragDropContext onDragEnd={onDragEnd}>
  <Droppable droppableId="list">
    {(provided) => (
      <ul {...provided.droppableProps} ref={provided.innerRef}>
        {items.map((item, index) => (
          <Draggable key={item.id} draggableId={item.id} index={index}>
            {(provided) => (
              <li ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
                {item.content}
              </li>
            )}
          </Draggable>
        ))}
        {provided.placeholder}
      </ul>
    )}
  </Droppable>
</DragDropContext>

react-movable:

<List
  values={items}
  onChange={({ oldIndex, newIndex }) => setItems(arrayMove(items, oldIndex, newIndex))}
  renderList={({ children, props }) => <ul {...props}>{children}</ul>}
  renderItem={({ value, props }) => <li {...props}>{value}</li>}
/>

A set of higher-order components to turn any list into an animated, accessible and touch-friendly sortable list✌️

Pros of react-sortable-hoc

  • More feature-rich, offering advanced functionalities like auto-scrolling and multi-list sorting
  • Highly customizable with extensive API options
  • Larger community and more frequent updates

Cons of react-sortable-hoc

  • Steeper learning curve due to its complexity
  • Larger bundle size, which may impact performance in smaller projects

Code Comparison

react-sortable-hoc:

import { SortableContainer, SortableElement } from 'react-sortable-hoc';

const SortableItem = SortableElement(({value}) => <li>{value}</li>);

const SortableList = SortableContainer(({items}) => {
  return (
    <ul>
      {items.map((value, index) => (
        <SortableItem key={`item-${index}`} index={index} value={value} />
      ))}
    </ul>
  );
});

react-movable:

import { List, arrayMove } from 'react-movable';

<List
  values={['Item 1', 'Item 2', 'Item 3']}
  onChange={({ oldIndex, newIndex }) =>
    setItems(arrayMove(items, oldIndex, newIndex))
  }
  renderList={({ children, props }) => <ul {...props}>{children}</ul>}
  renderItem={({ value, props }) => <li {...props}>{value}</li>}
/>

Both libraries provide drag-and-drop functionality for React applications, but react-sortable-hoc offers more advanced features at the cost of complexity, while react-movable focuses on simplicity and ease of use.

React bindings for SortableJS

Pros of react-sortablejs

  • More feature-rich, offering advanced sorting capabilities like multi-list sorting and nested sortables
  • Better performance for large lists due to its optimized drag-and-drop algorithm
  • Extensive documentation and a larger community, providing better support and resources

Cons of react-sortablejs

  • Larger bundle size, which may impact load times for smaller applications
  • Steeper learning curve due to its more complex API and configuration options
  • Less seamless integration with React's state management, requiring more manual handling

Code Comparison

react-sortablejs:

import { ReactSortable } from "react-sortablejs";

<ReactSortable list={items} setList={setItems}>
  {items.map(item => <div key={item.id}>{item.name}</div>)}
</ReactSortable>

react-movable:

import { List, arrayMove } from "react-movable";

<List
  values={items}
  onChange={({ oldIndex, newIndex }) =>
    setItems(arrayMove(items, oldIndex, newIndex))
  }
  renderList={({ children, props }) => <ul {...props}>{children}</ul>}
  renderItem={({ value, props }) => <li {...props}>{value}</li>}
/>

react-movable offers a simpler API and more React-like approach, while react-sortablejs provides more powerful sorting capabilities at the cost of complexity.

Drag-and-drop sortable component for nested data and hierarchies

Pros of react-sortable-tree

  • Supports nested tree structures, allowing for complex hierarchical data representation
  • Includes built-in node adding, removing, and searching functionality
  • Offers extensive customization options for node appearance and behavior

Cons of react-sortable-tree

  • Larger bundle size due to more features and dependencies
  • Steeper learning curve for implementation and customization
  • Less performant with very large datasets compared to simpler alternatives

Code Comparison

react-sortable-tree:

import SortableTree from 'react-sortable-tree';

<SortableTree
  treeData={this.state.treeData}
  onChange={treeData => this.setState({ treeData })}
  generateNodeProps={rowInfo => ({
    buttons: [<button onClick={() => this.removeNode(rowInfo)}>X</button>],
  })}
/>

react-movable:

import { List, arrayMove } from 'react-movable';

<List
  values={this.state.items}
  onChange={({ oldIndex, newIndex }) =>
    this.setState(prevState => ({
      items: arrayMove(prevState.items, oldIndex, newIndex),
    }))
  }
  renderList={({ children, props }) => <ul {...props}>{children}</ul>}
  renderItem={({ value, props }) => <li {...props}>{value}</li>}
/>
20,973

Drag and Drop for React

Pros of react-dnd

  • More comprehensive and feature-rich, supporting complex drag and drop scenarios
  • Highly customizable with a flexible API
  • Large community and extensive documentation

Cons of react-dnd

  • Steeper learning curve due to its complexity
  • Heavier bundle size, which may impact performance for simpler use cases

Code Comparison

react-dnd:

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

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

  return <div ref={drag}>{text}</div>;
}

react-movable:

import { List, arrayMove } from 'react-movable';

function MovableList({ items, setItems }) {
  return (
    <List
      values={items}
      onChange={({ oldIndex, newIndex }) =>
        setItems(arrayMove(items, oldIndex, newIndex))
      }
      renderList={({ children }) => <ul>{children}</ul>}
      renderItem={({ value, props }) => <li {...props}>{value}</li>}
    />
  );
}

react-movable offers a simpler API and is more focused on list reordering, making it easier to implement basic drag and drop functionality. However, react-dnd provides more control and flexibility for complex scenarios, at the cost of increased complexity and setup time.

🖱 A resizable and draggable component for React.

Pros of react-rnd

  • Supports both resizing and dragging of elements
  • Provides more customization options for handles and boundaries
  • Offers snap-to-grid functionality for precise positioning

Cons of react-rnd

  • Larger bundle size due to additional features
  • Steeper learning curve with more complex API
  • May have performance issues with many resizable elements

Code Comparison

react-rnd:

<Rnd
  default={{
    x: 0,
    y: 0,
    width: 320,
    height: 200,
  }}
>
  Resizable and draggable content
</Rnd>

react-movable:

<List
  values={['Item 1', 'Item 2', 'Item 3']}
  onChange={({ oldIndex, newIndex }) => {
    // Handle reordering logic
  }}
  renderList={({ children, props }) => <ul {...props}>{children}</ul>}
  renderItem={({ value, props }) => <li {...props}>{value}</li>}
/>

react-movable focuses primarily on list reordering, while react-rnd offers more comprehensive element manipulation. react-movable has a simpler API and is better suited for straightforward drag-and-drop list functionality. react-rnd, on the other hand, provides a wider range of features for resizing and positioning elements, making it more versatile but potentially more complex to implement.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

react-movable

npm version npm downloads size stackblitz

Basic list

See all the other examples and their source code! Try it out in the Stackblitz sandbox!

Installation

pnpm add react-movable

Usage

import * as React from "react";
import { List, arrayMove } from "react-movable";

const SuperSimple: React.FC = () => {
  const [items, setItems] = React.useState(["Item 1", "Item 2", "Item 3"]);
  return (
    <List
      values={items}
      onChange={({ oldIndex, newIndex }) =>
        setItems(arrayMove(items, oldIndex, newIndex))
      }
      renderList={({ children, props }) => <ul {...props}>{children}</ul>}
      renderItem={({ value, props }) => <li {...props}>{value}</li>}
    />
  );
};

Features

  • Vertical drag and drop for your lists and tables
  • No wrapping divs or additional markup
  • Simple single component, no providers or HoCs
  • Unopinionated styling, great for CSS in JS too
  • Accessible, made for keyboards and screen readers
  • Touchable, works on mobile devices
  • Full control over the dragged item, it's a portaled React component
  • Autoscrolling when dragging (both for containers and the window)
  • Scrolling with the mousewheel / trackpad when dragging
  • Works with semantic table rows too
  • Smooth animations, can be disabled
  • Varying heights of items supported
  • Optional lock of the horizontal axis when dragging
  • No dependencies, less than 4kB (gzipped)
  • Coverage by e2e playwright tests

Keyboard support

  • tab and shift+tab to focus items
  • space to lift or drop the item
  • j or arrow down to move the lifted item down
  • k or arrow up to move the lifted item up
  • escape to cancel the lift and return the item to its initial position

<List /> props

renderList

renderList: (props: {
  children: React.ReactNode;
  isDragged: boolean;
  props: {
    ref: React.RefObject<any>;
  };
}) => React.ReactNode;

renderList prop to define your list (root) element. Your function gets three parameters and should return a React component:

  • props containing ref - this needs to be spread over the root list element, note that items need to be direct children of the DOM element that's being set with this ref
  • children - the content of the list
  • isDragged - true if any item is being dragged

renderItem

renderItem: (params: {
  value: Value;
  index?: number;
  isDragged: boolean;
  isSelected: boolean;
  isDisabled: boolean;
  isOutOfBounds: boolean;
  props: {
    key?: number;
    tabIndex?: number;
    "aria-roledescription"?: string;
    onKeyDown?: (e: React.KeyboardEvent) => void;
    onWheel?: (e: React.WheelEvent) => void;
    style?: React.CSSProperties;
    ref?: React.RefObject<any>;
  };
}) => React.ReactNode;

renderItem prop to define your item element. Your function gets these parameters and should return a React component:

  • value - an item of the array you passed into the values prop
  • index - the item index (order)
  • isDragged - true if the item is dragged, great for styling purposes
  • isDisabled - true if the list is disabled or value.disabled is true, great for styling purposes
  • isSelected - true if the item is lifted with the space
  • isOutOfBounds - true if the item is dragged far left or right
  • props - it has multiple props that you need to spread over your item element. Since one of these is ref, if you're spreading over a custom component, it must be wrapped in React.forwardRef like in the "Custom component" example.

values

values: Value[]

An array of values. The value can be a string or any more complex object. The length of the values array equals the number of rendered items.

onChange

onChange: (meta: { oldIndex: number; newIndex: number, targetRect: ClientRect }) => void

Called when the item is dropped to a new location:

  • oldIndex - the initial position of the element (0 indexed)
  • newIndex - the new position of the element (0 indexed), -1 when removableByMove is set and item dropped out of bounds
  • targetRect - getBoundingClientRect of dropped item

The List component is stateless and controlled so you need to implement this function to change the order of input values. Check the initial example.

beforeDrag

beforeDrag?: (params: { elements: Element[]; index: number }) => void;

Called when a valid drag is initiated. It provides a direct access to all list DOM elements and the index of dragged item. This can be useful when you need to do some upfront measurements like when building a table with variable column widths.

removableByMove

removableByMove: boolean;

Default is false. When set to true and an item is dragged far left or far right (out of bounds), the original gap disappears (animated) and following item drop will cause onChange being called with newIndex = -1. You can use that to remove the item from your values state. Example.

transitionDuration

transitionDuration: number;

The duration of CSS transitions. By default it's 300ms. You can set it to 0 to disable all animations.

lockVertically

lockVertically: boolean;

If true, the dragged element can move only vertically when being dragged.

disabled

disabled: boolean;

If true, none of the items in the list will be draggable.

voiceover

voiceover: {
  item: (position: number) => string;
  lifted: (position: number) => string;
  dropped: (from: number, to: number) => string;
  moved: (position: number, up: boolean) => string;
  canceled: (position: number) => string;
}

In order to support screen reader users, react-movable is triggering different messages when user is interacting with the list. There is already a set of English messages included but you can override it with this prop.

container

container?: Element;

Provide custom DOM element where moved item will be rendered.

arrayMove and arrayRemove

There are also additional two helper functions being exported:

arrayMove: <T>(array: T[], from: number, to: number) => T[];
arrayRemove: <T>(array: T[], index: number) => T[];

They are useful when you need to manipulate the state of values when onChange is triggered.

Motivation

There are two main ways how you can implement drag and drop today:

There are multiple great libraries in React's ecosystem already. DnD can get pretty complicated so each one of them covers different use-cases and has different goals:

react-dnd is a general purpose DnD library that makes amazing job abstracting quirky HTML5 API. It provides well thought out lower-level DnD primitives and let you build anything you want.

react-beautiful-dnd is a really beautiful DnD library for lists. It comes with a great support for accessibility and it's packed with awesome features. It doesn't use HTML5 API so it doesn't impose any of its limitations.

react-sortable-hoc provides a set of higher order components to make your lists dnd-able. It has many features and approaches similar to react-beautiful-dnd but it's more minimalistic and lacks some features as accessibility or unopinionated styling.

So why react-movable was created? There are two main goals:

  • Small footprint. It's about 10 times smaller than react-dnd or react-beautiful-dnd (~3kB vs ~30kB) and half of the size of react-sortable-hoc (~7kB). That's especially important when you intend to use react-movable as a dependency in your own library. However, that also means that some features are left out - for example, the horizontal DnD is not supported.
  • Simple but not compromised. - Every byte counts but not if it comes down to the support for accessibility, screen readers, keyboards and touch devices. The goal is to support a limited set of use cases but without compromises.

Features that are not supported (and never will be)

  • Horizontal sorting.
  • DnD between multiple list.
  • Combining items / multi drag support.
  • Supporting older versions of React. The minimum required version is 16.3 since the new createRef and createPortal APIs are used.

If you need the features above, please give a try to react-beautiful-dnd. It's a really well-designed library with all those features and gives you a lot of power to customize! If you are building an application heavy on DnD interactions, it might be your best bet! react-movable's goal is not to be feature complete with react-beautiful-dnd.

Planned features

  • Built-in virtualization / windowing.

Other feature requests will be thoroughly vetted. Again, the primary goal is to keep the size down while supporting main use-cases!

End to end testing

This library is tightly coupled to many DOM APIs. It would be very hard to write unit tests that would not involve a lot of mocking. Or we could re-architect the library to better abstract all DOM interfaces but that would mean more code and bigger footprint.

Instead of that, react-movable is thoroughly tested by end to end tests powered by puppeteer. It tests all user interactions:

All tests are automatically ran in Github Actions with headless chromium. This way, the public API is well tested, including pixel-perfect positioning. Also, the tests are pretty fast, reliable and very descriptive. Running them locally is easy:

pnpm test:e2e

Browser support

  • Chrome (latest, mac, windows, iOS, Android)
  • Firefox (latest, mac, windows)
  • Safari (latest, mac, iOS)
  • Edge (latest, windows)

Users

If you are using react-movable, please open a PR and add yourself to this list!

Contributing

This is how you can spin up the dev environment:

git clone https://github.com/tajo/react-movable
cd react-movable
pnpm install
pnpm ladle serve
pnpm test
pnpm typecheck

Learning more

I wrote an article about Building a Drag and Drop List.

Also, gave a talk at React Advanced London: What a Drag (2019):

React Advanced London: What a Drag

Shoutouts 🙏

The popular React DnD libraries were already mentioned in the motivation part. Big shoutout to react-beautiful-dnd ❤️ ️ for supporting multiple great features and adding first-class support for accessibility! It was strongly used as an inspiration for react-movable!

Author

Vojtech Miksu 2024, miksu.cz, @vmiksu

NPM DownloadsLast 30 Days