---
title: DragDrop
category: General
summary: Custom components that wrap [react-beautiful-dnd](https://github.com/atlassian/react-beautiful-dnd/). Uses the same API exported under `Provider`, `Droppable`, and `Draggable` components.
---

```jsx live
const navigationItems = [
  { icon: 'house', name: 'Home' },
  { icon: 'person', name: 'Profile' },
  { icon: 'checkmark-circle-filled', name: 'Check-In' },
  { icon: 'people', name: 'Groups' },
  { icon: 'calendar', name: 'Events' },
  { icon: 'search', name: 'Search' },
]
function Item({
  icon,
  name,
  type,
  index,
  onAdd,
  onRemove,
  hoverEnabled = true,
  ...restProps
}) {
  const { hover, hoverProps } = hooks.useHover()
  return (
    <Card
      axis="horizontal"
      alignment="center"
      marginBottom={1}
      padding={1}
      spacing={1}
      elevation={1}
      borderColor="transparent"
      hover={hoverEnabled ? { borderColor: 'blue-5' } : {}}
      {...hoverProps}
      {...restProps}
    >
      <Icon name={icon} color="grey-7" />
      <Text grow={1}>{name}</Text>
      <Button
        title={type === 'remove' ? 'Remove' : 'Add'}
        icon={{
          name: `${type}-circle-filled`,
          color: type === 'remove' ? 'error' : 'success',
        }}
        variant="naked"
        opacity={hover && hoverEnabled ? 1 : 0}
        transform={`scale(${hover && hoverEnabled ? 1 : 0.9})`}
        transition="all 280ms ease-out"
        onClick={() => (type === 'remove' ? onRemove() : onAdd({ icon, name }))}
      />
    </Card>
  )
}
function App() {
  const [activeNavigationItems, setActiveNavigationItems] = React.useState(
    navigationItems.slice(1, 3)
  )
  const filteredNavigationItems = navigationItems.filter((item) =>
    activeNavigationItems.every((activeItem) => activeItem.name !== item.name)
  )
  return (
    <DragDrop.Provider>
      <GridView width="100%" columns="1fr 240px">
        <StackView padding={6} spacing={2}>
          <DragDrop.Droppable
            droppableId="navigation"
            onDrop={(event) => {
              if (event.source.droppableId === 'static') {
                setActiveNavigationItems(
                  utils.insertAtIndex({
                    list: activeNavigationItems,
                    index: event.destination.index,
                    value: filteredNavigationItems[event.source.index],
                  })
                )
              } else {
                setActiveNavigationItems(
                  utils.reorderArray({
                    list: activeNavigationItems,
                    startIndex: event.source.index,
                    endIndex: event.destination.index,
                  })
                )
              }
            }}
          >
            {(provided, snapshot) => (
              <Card
                innerRef={provided.innerRef}
                subdued
                minWidth={32}
                paddingTop={1}
                paddingHorizontal={1}
              >
                {activeNavigationItems.map((item, index) => (
                  <DragDrop.Draggable
                    key={item.name}
                    draggableId={item.name}
                    index={index}
                  >
                    {(provided, snapshot) => (
                      <Item
                        type="remove"
                        innerRef={provided.innerRef}
                        onRemove={() => {
                          setActiveNavigationItems((activeNavigationItems) => {
                            const nextItems = [...activeNavigationItems]
                            nextItems.splice(index, 1)
                            return nextItems
                          })
                        }}
                        {...provided.draggableProps}
                        {...provided.dragHandleProps}
                        {...item}
                      />
                    )}
                  </DragDrop.Draggable>
                ))}
                {provided.placeholder}
              </Card>
            )}
          </DragDrop.Droppable>
        </StackView>
        <StackView padding={3} spacing={1} backgroundColor="grey-1">
          <DragDrop.Droppable
            droppableId="static"
            onDragStart={console.log}
            onDragEnd={console.log}
            sourceOnly
          >
            {(provided) => (
              <StackView innerRef={provided.innerRef}>
                {filteredNavigationItems.map((item, index) => (
                  <DragDrop.Draggable
                    key={item.name}
                    draggableId={item.name}
                    index={index}
                    placeholder={() => (
                      <Item type="add" opacity={0.65} {...item} />
                    )}
                  >
                    {(provided, snapshot) => (
                      <Item
                        type="add"
                        innerRef={provided.innerRef}
                        onAdd={(item) =>
                          setActiveNavigationItems((activeNavigationItems) => [
                            ...activeNavigationItems,
                            item,
                          ])
                        }
                        {...provided.draggableProps}
                        {...provided.dragHandleProps}
                        {...item}
                      />
                    )}
                  </DragDrop.Draggable>
                ))}
                {provided.placeholder}
              </StackView>
            )}
          </DragDrop.Droppable>
        </StackView>
      </GridView>
    </DragDrop.Provider>
  )
}
render(<App />)
```

## Props

Please refer to [react-beautiful-dnd](https://github.com/atlassian/react-beautiful-dnd/) for documentation on events and setting up drag and drop lists. In addition to the default functionality provided, we augment the library with the following props:

### DragDrop.Droppable

`onDragStart: (event) => void`

Called at the start of a child `Draggable` drag.

`onDragEnd: (event) => void`

Called at the end of a child `Draggable` drag.

`onDrop: (event) => void`

This function will be called when a `Draggable` has been dropped. Returns the `onDragEnd` event with any `Draggable` `data` attached.

`sourceOnly: boolean`

This prop controls whether or not the droppable can receive and reorder draggables. If `true`, draggables may move outside the droppable, but not added.

### DragDrop.Draggable

`data: any`

An additional `data` prop has been added to flow data more easily between components that will be attached to the `onDragEnd` and `onDrop` events.

`placeholder: () => React.ReactNode`

The `placeholder` prop allows a custom placeholder to be rendered when used in conjunction with the `sourceOnly` prop on `Droppable`.
