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

Overview

react-movable

npm version npm downloads Build Status size

Basic list

Edit Basic react-movable

See all the other examples and their source code!

Installation

yarn 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
  • Typescript and Flow type definitions
  • No dependencies, less than 4kB (gzipped)
  • Coverage by e2e puppeteer 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;
  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 5 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
  • 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.

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 Travis CI 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.

Do you want to run them in the dev mode (slows down operations, opens the browser)?

yarn ladle serve #start the ladle server
yarn test:e2e:dev #run the e2e tests
yarn test:e2e

Browser support

  • Chrome (latest, mac, windows, iOS, Android)
  • Firefox (latest, mac, windows)
  • Safari (latest, mac, iOS)
  • Edge (latest, windows)
  • MSIE 11 (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
yarn
yarn ladle serve

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!

BrowserStack Logo

Big thanks to BrowserStack for letting the maintainers use their service to debug browser issues.

And Netlify for free hosting.

Author

Vojtech Miksu 2019, miksu.cz, @vmiksu

Comments
  • isDragDisabled / allowing only some items to be draggable

    isDragDisabled / allowing only some items to be draggable

    Thanks for making this awesome lib! We are replacing react-beuatiful-dnd as this is smaller and easier to handle, and it works fantastic so far but there's one thing i can't find. In our list only one element can be dragged, the others shouldn't be. In b-dnd there's a isDragDisabled flag, is there something like this here? And if not, could it be added?

    enhancement 
    opened by drcmda 14
  • Configurable scroll speed

    Configurable scroll speed

    Hi. I feel the scroll speed increases pretty quickly on drag. If the list size is small that speed seems too much. Is there a way to slow that down? Maybe allow to provide a speed function as configuration?

    enhancement good first issue 
    opened by Munawwar 13
  • Moving table rows with percentage-based style width?

    Moving table rows with percentage-based style width?

    I’ve been trying to figure out how to correctly render a draggable table where the rows & data cells use a percentage-based width. The problem I encounter is that when a row is dragging (isDragged is true) the data cells collapse to fit the width of their content. I’ve mostly followed the table example from here, but changed the styles and data to match my own app. My isDragged row is wrapped in a <table> element in the renderItem function, with the same styles as the regular <table> in the renderList.

    It seems like no matter how I style the table, the isDragged row will always collapse unless I use pixel units for its width.

    I tried following some of the advice from react-beautiful-dnd (here) but it doesn’t seem to apply to react-movable (or I’m just doing it incorrectly).

    opened by johnridesabike 8
  • Is it possible to limit upper index?

    Is it possible to limit upper index?

    We have a list of elements, say

    Feature A
    Feature B
    Feature C
    -------------
    Feature D
    Feature E
    

    The bar (-------------) represents a rollbar that can be dragged up or down to mark the end of a sequence. It can't (shouldn't) go higher than, say, A or B. How can i specify that indicies 0-1 are off limits for it?

    question 
    opened by drcmda 8
  • 3.0.1 is broken

    3.0.1 is broken

    When trying to use 3.0.1 in a webpack project:

    ERR! Module not found: Error: Can't resolve './List' in '/mnt/agent/work/3f8925a0bfceb8d1/node_modules/react-movable/lib'
    ERR! Did you mean 'List.js'?
    ERR! BREAKING CHANGE: The request './List' failed to resolve only because it was resolved as fully specified
    ERR! (probably because the origin is strict EcmaScript Module, e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '"type": "module"').
    ERR! The extension in the request is mandatory for it to be fully specified.
    ERR! Add the extension to the request.
    

    you should probably add extension to all imports, as stated here: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-move-my-commonjs-project-to-esm

    Use only full relative file paths for imports: import x from '.'; → import x from './index.js';.

    You must use a .js extension in relative imports even though you're importing .ts files.

    opened by Hypnosphi 6
  • De-selecting item on blur

    De-selecting item on blur

    I'm not sure this is an issue or it's a design choice but here's what I'm currently experiencing:

    I select and item by pressing space so I can move it up and down using the keyboard. Before pressing space to place it, I click somewhere outside of the list. I would expect the item to go back to where it was and be de-selected but instead it stays selected. It seems like the isSelected flag is not set to false when onblur event happens.

    Am I missing something and this is desired behaviour or is this a bug?

    bug good first issue 
    opened by corn-eliu 6
  • Errors if an item is being dragged when the parent component is onmounted

    Errors if an item is being dragged when the parent component is onmounted

    utils.js:32 Uncaught TypeError: Cannot read property 'style' of null at Object.transformItem (utils.js:32) at List._this.onMove (List.js:165) at List._this.onMouseMove (List.js:150) at utils.js:80


    No items found in the List container. Did you forget to pass & spread the props param in renderList?


    react-dom.development.js:506 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method. in List (created by Sort) in div (created by Sort) in Sort (created by ConnectFunction) in ConnectFunction (created by ActiveElement)

    wontfix 
    opened by badtant 6
  • fix: memory leak, clearTimeout and cancelAnimationFrame on componentWillUnmount

    fix: memory leak, clearTimeout and cancelAnimationFrame on componentWillUnmount

    closes https://github.com/tajo/react-movable/issues/73

    When the List component is removed (via a click/touch), requestAnimationFrame is executed but not finished, so results in a memory leak.

    this PR returns a cancel function from the schd util so we can cancel it on unmount. Also added clearTimeout just in case

    bug 
    opened by bryan-hunter 5
  • The onKeyDown handler eats the event of any nested Textarea.onChange handlers.

    The onKeyDown handler eats the event of any nested Textarea.onChange handlers.

    Hi React-Movable folks,

    Just a heads up - a text area component that was deeply nested inside a renderItem wasn't receiving an onChange event when the text area was updated.

    The fix was to remove the onKeyDown props that was being applied way up the component tree.

    May be worth adding this to the docs as it was super tricky to debug.

    Thanks for your awesome library.

    Best wishes,

    Sam

    duplicate help wanted 
    opened by shenders13 5
  • Expose more events

    Expose more events

    I appreciate your focus on minimalism and sticking to just the essentials! It's a great library. I'm wondering if any of the following additions would make the cut to be added?

    1. onDragEnd event that fires regardless of whether a change occurs or not. I'm using beforeDrag to track when dragging is actually occurring and customizing the UI. But I need custom logic to determine when it stops.
    2. Send the clientX and clientY positions as part of the beforeDrag event.
    3. onMove event that includes clientX and clientY. I think 2&3 would allow for greater customization without adding any bulk to the library. Personally, I'd like to use 2&3 to change the cursor to a grabber only once the item has been dragged > 5px. Why? Because it's clickable until it's been dragged a few pixels (similar to Trello drag and drop).
    enhancement 
    opened by danbriggs5 5
  • cant use

    cant use "rest of item" to scroll when i have data-movable-handle

    So, I have items with a drag handle (data-movable-handle). When touching and dragging on the rest of the item I expect the page to scroll. It doesn't.

    bug good first issue 
    opened by badtant 5
  • Provide option to disable auto-scrolling

    Provide option to disable auto-scrolling

    Thank you so much for creating such minimal version of a drag-n-drop list for react.

    Would it be possible to have a parameter to disable the auto-scrolling behavior?

    Context

    There are issues with smaller component heights, but also small "draggable" items whose drag-n-drop is not stable enough to be used in some scenarios without a detrimental experience.

    Related

    • #80
    • #68
    • Abandoned PR: https://github.com/tajo/react-movable/pull/78

    Different perspective

    I understand that auto-scroll is a nice feature to have -- if it works. But since it doesn't always work, being able to disable it would be great.

    My use case scenarios don't involve moving items very far in the list even though the list can be long. This suggests that I don't need auto-scroll, yet having it enabled right now makes the drag-n-drop hazardous.

    I need the scrolling to be possible, but not done automatically when dragging items.

    Using transitionDuration={0} helps in making the whole behavior more reasonable, but it's still not sufficient in some scenarios where a small element ends up being scrolled down very far in a short period of time.

    opened by xionluhnis 1
  • Unable to forbid certain item to move at certain index

    Unable to forbid certain item to move at certain index

    @tajo hi and thank you for your library! I wonder are there any options in onChange handler to forbid to move certain item to certain position (for example at the start of the list) ? Cannot get the value of the dragged element.

    opened by vtarelkin 1
  • Support for React Portal inside the list

    Support for React Portal inside the list

    There is an error that occurs when you're using a React Portal inside a list item, in my case a Modal from react-bootstrap.

    When a keyboard event is dispatched inside the portal, it bubbles up the React component tree to react-movable's onKeyDown handler, which calls checkIfInteractive(target: Element, rootElement: Element). But in this case the DOM tree doesn't match the React tree, and because of that target is not a descendant of rootElement. As a result, in the code inside checkIfInteractive, target ends up being null, and an error is thrown at this line: https://github.com/tajo/react-movable/blob/daf3d1f7494903498c3dde450b612596b9be438d/src/utils.ts#L119

    opened by ivan7237d 0
  • Expose needle position and currently dragging index

    Expose needle position and currently dragging index

    Hi here,

    Firstly thank you for this library!

    This PR includes 2 changes.

    1. itemDragged is exposed to the renderItem function
    2. needle is exposed to the renderList function as a getter prop getNeedle

    This change allows for some more flexible styling based on the "needle" position.

    opened by ben-styling 2
  • Nested sortable table with drag handle: dragging children table row moves parent table row

    Nested sortable table with drag handle: dragging children table row moves parent table row

    Have not had a chance to create a Sandbox yet, but just reporting in case there is a trivial workaround.

    I have a use case of having a table with sortable rows, which each row containing another table with sortable row. I am using drag handle (data-movable-handle).

    In this case, I see the issue that dragging one row from the nested table sometimes drags the parent row from the parent table. Would there be an easy workaround for fixing this?

    Maybe providing the ability to customize the "data-movable-handle" property name so there is no conflict between the 2 components?

    opened by yann-combarnous 0
Releases(v3.0.0)
  • v3.0.0(Nov 3, 2021)

  • v2.5.0(May 1, 2020)

  • v2.4.0(Dec 11, 2019)

    When you start putting things like inputs into your list items, you could get into troubles. React Movable is now smarter and ignores mouse/keyboard inputs when you are trying to type or click. There's a new example:

    Screen Shot 2019-12-11 at 3 35 01 PM

    Also, all deps are updated and React Hooks in all examples.

    Source code(tar.gz)
    Source code(zip)
  • v2.3.0(Oct 16, 2019)

    The drop is now animated so it provides user a nice visual feedback. Previously, it just re-appeared in the final position. The length of animation is controlled by the transitionDuration prop.

    This release also fixes a bug when an item move far top didn't end up reordered.

    Before

    old

    After

    animated

    Source code(tar.gz)
    Source code(zip)
  • v2.2.0(Jul 2, 2019)

    There is a new removableByMove top API that will allow you to remove items when being moved far left or right. PR. Example.

    removable

    This also adds targetRect as an another parameter that's being passed to you through onChange callback. This can be useful when you want to detect where exactly the item was dropped.

    Source code(tar.gz)
    Source code(zip)
  • v2.1.1(Jul 1, 2019)

    There is a new top level API beforeDrag that allows to do some measurements of elements and implement a table with dynamic column widths.

    Items now recognize disable: true prop that prevents them to be dragged. Example.

    /* ... */
    state = {
        items: [
          { label: 'Item 1' },
          { label: 'Item 2' },
          { label: 'Item 3' },
          { label: 'Item 4', disabled: true },
          { label: 'Item 5', disabled: true },
          { label: 'Item 6' }
        ]
      };
      render() {
        return (
          <List
            values={this.state.items}
    /* ... */
    
    Source code(tar.gz)
    Source code(zip)
  • v2.0.0(Feb 11, 2019)

    You should notice a big improvement if you are using Safari on iOS. The drag and drop action will now block the scrolling. The previous version didn't do that and it made react-movable almost unusable on iOS.

    It was necessary to overhaul some internal event handling. There is a slight API change in case you are using custom handle target. You should now mark it with data-movable-handle attribute as you can see here.

    It's simple like this:

    <button
      data-movable-handle
    >
      MOVE HANDLE
    </button>
    

    If no element inside of the list item has data-movable-handle, the whole item is draggable.

    renderItem is not passing onMouseDown and onTouchStart into your list items anymore. It's now handled globally via document.addEventListener. That was necessary to set these events as non-passive and enable scroll blocking in iOS Safari.

    Also, you don't have to anymore event.stopPropagation on interactive elements (buttons etc) that are placed inside of your list items. Those are now filtered automatically when react-movable considers if the dnd action started.

    So unless, you were somehow utilizing onMouseDown or onTouchStart, you can safely upgrade.

    Flow types were fixed and updated as well.

    Source code(tar.gz)
    Source code(zip)
Owner
Vojtech Miksu
Core Infrastructure at Uber.
Vojtech Miksu
A modern, lightweight, performant, accessible and extensible drag & drop toolkit for React.

A modern, lightweight, performant, accessible and extensible drag & drop toolkit for React.

Claudéric Demers 6.5k Jan 7, 2023
Drag and drop page builder and CMS for React, Vue, Angular, and more

Drag and drop page builder and CMS for React, Vue, Angular, and more Use your code components and the stack of your choice. No more being pestered for

Builder.io 4.2k Jan 1, 2023
React drag and drop sort support flex layout and nested.

react-flex-dnd React drag and drop sort support flex layout and nested. This package using hooks, note that your React version is above 16.8 :) Why fl

lvshihao 7 Nov 14, 2022
ReactJS drag and drop functionality for mouse and touch devices

DragDropContainer and DropTarget Live demo: peterh32.github.io/react-drag-drop-container Features Very easy to implement and understand. Works on mous

Peter Hollingsworth 142 Sep 26, 2022
Drag and Drop for React

React DnD Drag and Drop for React. See the docs, tutorials and examples on the website: http://react-dnd.github.io/react-dnd/ See the changelog on the

React DnD 18.7k Jan 7, 2023
Drag and Drop for React

React DnD Drag and Drop for React. See the docs, tutorials and examples on the website: http://react-dnd.github.io/react-dnd/ See the changelog on the

React DnD 18.7k Jan 6, 2023
Drag and Drop for React

Drag and Drop for React

React DnD 18.7k Jan 4, 2023
Light React Drag & Drop files and images library styled by styled-components

Light React Drag & Drop files and images library styled by styled-components

null 143 Dec 28, 2022
React Drag and Drop file input

React Drag and Drop file input

Tran Anh Tuat 45 Dec 30, 2022
React drag and drop framework with inbuilt virtualizing scrollbars.

About us This library was made by Forecast - powered by AI, Forecast is supporting your work process with a complete Resource & Project Management pla

Forecast 51 Sep 21, 2022
Drag and Drop library for React.

react-tiny-dnd Drag and Drop library for React. Demo Install via npm npm install react-tiny-dnd or yarn add react-tiny-dnd Features Vertical lists Eas

Rafael Hovhannisyan 27 Nov 27, 2022
Creating an app using Drag and Drop with React without libraries 🤏

Creating an app using Drag and Drop with React without libraries ?? ! This time, we are going to implement the functionality to do a Drag & Drop with

Franklin Martinez 5 Sep 23, 2022
:ok_hand: Drag and drop so simple it hurts

Drag and drop so simple it hurts Official React wrapper for dragula. Demo Try out the demo! Install You can get it on npm. npm install react-dragula -

Nicolás Bevacqua 976 Nov 26, 2022
A directive based drag and drop container for solid-js

A directive based drag and drop container for solid-js

Isaac Hagoel 52 Dec 8, 2022
🦋 Component for building file fields - from basic file inputs to drag and drop image galleries.

?? react-butterfiles A small component for building file upload fields of any type, for example a simple file upload button or an image gallery field

Adrian Smijulj 45 Aug 26, 2022
Simple HTML5 drag-drop zone with React.js.

react-dropzone Simple React hook to create a HTML5-compliant drag'n'drop zone for files. Documentation and examples at https://react-dropzone.js.org.

null 9.4k Jan 2, 2023
example how to use react-dropzone for drag 'n drop uploader

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

Hendra_Adri 1 Nov 5, 2021
Taskboard with drag'n drop feature. Built w/ React, TypeScript

Drag & Drop Taskboard A taskboard application with drag and drop feature. Live demo is here. Tech Stack Language: TypeScript UI-Components: Ant Design

Onur Ă–nder 37 Dec 16, 2022
"Drag to resize" (sizing) as React Component.

react-drag-sizing "Drag to resize" (sizing) as React Component Rewritten with TS & React-hooks Polyfill workaround with React < 16.8 Support both mous

Fritz Lin 13 Nov 7, 2022