Home

Awesome

react-movable

npm version npm downloads size <a href="https://stackblitz.com/edit/react-movable?file=src%2FApp.tsx"><img src="https://img.shields.io/badge/stackblitz-sandbox-orange" alt="stackblitz"></a>

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

Keyboard support

<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:

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:

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: DOMRect }) => void

Called when the item is dropped to a new location:

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.

afterDrag

afterDrag?: (params: { elements: Element[]; oldIndex: number; newIndex: number }) => void;

Called when a drag is completed. It provides a direct access to all list DOM elements, the old as well as the new index of the dragged item. This can be useful when you need to perform some cleanup, e.g. in conjunction with beforeDrag. Please note this is different to onChange, which will only fire when the drag results in a changed order.

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:

Features that are not supported (and never will be)

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

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

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