---
order: 0
title: Drop targets
description: Drop targets are elements that items can be dropped on.
---

import SectionMessage from '@atlaskit/section-message';
import NestedDropTargetExample from '../../../examples/nested-drop-targets';

Drop targets are elements that can be dropped upon by something that is dragging. There are
different drop targets for different types of draggable items, such as elements or files.

## Basic usage

```ts
// drop targets are exposed through adapters
import { dropTargetForExternal } from '@atlaskit/pragmatic-drag-and-drop/external/adapter';

const cleanup = dropTargetForExternal({
	element: myElement,
});
```

```ts
// basic usage with react
import React, { useEffect, useRef } from 'react';
import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';

function DropTarget() {
	const ref = useRef<HTMLDivElement | null>(null);

	useEffect(() => {
		const el = ref.current;
		if (!el) {
			throw new Error('ref not set correctly');
		}

		return dropTargetForElements({
			element: el,
		});
	}, []);

	return <div ref={ref}>Drop elements on me!</div>;
}
```

## Rules

- Drop targets are scoped to a particular entity type. For example, `dropTargetForElements` is a
  _drop target_ for elements, and `dropTargetForExternal` is a drop target for files.
- A single `element` can be used as a drop target for multiple entity types.

```ts
// ✅ Using the same element as a drop target for elements and for files
const cleanup = combine(
	dropTargetForElements({
		element: myElement,
	}),
	dropTargetForExternal({
		element: myElement,
	}),
);
```

- A single `element` cannot be used to create multiple drop targets for the same entity type (you
  will get a warning in your `console` if you make a mistake).

```ts
// ❌ Using the same element for two drop targets of the same entity type is not allowed
const cleanup = combine(
	dropTargetForElements({
		element: myElement,
	}),
	// ⚠️ A warning will be logged if this is detected
	dropTargetForElements({
		element: myElement,
	}),
);
```

- Drop targets can be nested.

```ts
dropTargetForElements({
	element: parentElement,
});
dropTargetForElements({
	element: childElement,
});
```

<Example Component={NestedDropTargetExample} appearance="showcase-only" />

- During a drag operation:
  - You can add new _drop targets_
  - You can remove _drop targets_
  - You can _remount_ a _drop targets_ (see
    [reconciliation](/components/pragmatic-drag-and-drop/core-package/reconciliation))
  - You can change the dimensions of any _drop target_

## Drop target arguments

High level:

- `element` - the `Element` the drop target will be attached to
- `getData()` - data to associate with the drop target
- `canDrop()` - whether the drop target can be dropped on
- `getDropEffect()` - control the cursor when over a drop target
- `getIsSticky()` - whether the drop target will hold onto selection after no longer being dragged
  over
- [`onGenerateDragPreview`](/components/pragmatic-drag-and-drop/core-package/events)
- [`onDragStart`](/components/pragmatic-drag-and-drop/core-package/events)
- [`onDrag`](/components/pragmatic-drag-and-drop/core-package/events)
- [`onDropTargetChange`](/components/pragmatic-drag-and-drop/core-package/events)
- [`onDrop`](/components/pragmatic-drag-and-drop/core-package/events)

### `element` (required)

```ts
element: Element;
```

The `Element` you want to attach the drop target to.

### `getData`

```ts
getData?: (args: GetFeedbackArgs) => Record<string, unknown>;
```

`getData()` is a function that returns data you want to attach to the drop target.

`getData()` is called with `GetFeedbackArgs` (see below) which contains limited information about
the current drag operation. Try to make your `getData()` function _pure_ (same input results in the
same output)

```ts
const cleanup = dropTargetForExternal({
	element: myElement,
	getData: () => ({ id: 'Alex' }),
});
```

`getData()` is called _repeatedly_ while the user is dragging over the drop target in order to power
addons. If your `getData()` function is expensive, consider using the
[`once` utility function](/components/pragmatic-drag-and-drop/core-package/utilities).

If you want to understand the _type_ of data attached to a drop target elsewhere in your
application, see our
[typing data guide](/components/pragmatic-drag-and-drop/core-package/recipes/typing-data).

### `canDrop`

```ts
canDrop?: (args: GetFeedbackArgs) => boolean
```

`canDrop()` is used to conditionally block dropping on a drop target.

Returning `false` from `canDrop()` will not block dropping on parent or child drop targets. All drop
targets that want to not allow dropping, need to return `false` from their `canDrop()` function.

When looking for valid drop targets, a lookup starts from the deepest part of the DOM tree the user
is currently over and searches upwards for valid targets. If a drop target blocks dragging
(`canDrop()` returns `false`), then that drop target is ignored and the search upwards continues.

`canDrop()` is called _repeatedly_ while a drop target is being dragged over to allow you to
dynamically change your mind as to whether a drop target can be dropped on (including after it has
already been entered into). This could be helpful in a situation where you are waiting on some
permission information from a backend service.

```ts
// I can never be dropped on!
dropTargetForExternal({
	element: myElement,
	canDrop: () => false,
});

// only allow 'cards' to be dropped on this drop target
dropTargetForElements({
	element: myOtherElement,
	canDrop: ({ source }: GetFeedbackArgs) => {
		return source.data.type === 'card';
	},
});
```

```ts
type GetFeedbackArgs = {
	input: Input;
	source: SourcePayload; // this payload type will be different for different adapters
	element: Element;
};
```

### `getDropEffect`

```ts
getDropEffect?: (args: GetFeedbackArgs) => DataTransfer['dropEffect']
```

The `dropEffect` property will control the visual feedback (cursor) when dragging over it. As with
`getData()`, `getDropEffect()` is repeatedly called throughout a drag operation. The default
`dropEffect` is dependent on the adapter.

```ts
dropTargetForElements({
	getDropEffect: () => 'link',
});
```

`getDropEffect()` is called _repeatedly_ while a drop target is being dragged over to allow you
change your mind about which drop effect should be applied.

When working with nested _drop targets_, the inner most drop targets `dropEffect` is the one that
will be applied; even if inner most drop target is using the default value (`"move"`).

<SectionMessage>

For more information about controlling the users cursor while dragging, see
[our cursor guide](/components/pragmatic-drag-and-drop/web-platform-design-constraints#cursors)

</SectionMessage>

### `getIsSticky`

```ts
getIsSticky?: (args: GetFeedbackArgs) => boolean;
```

Used to control whether a drop target is "sticky" or not.

Drop targets are generally calculated based on where the user's pointer is currently located. In
some scenarios you might want to _hold on_ to a previous drop target (make it "sticky"), even when
the drop target is no longer being directly dragged over. This is useful if you want to maintain a
selection while you are in _gaps_ between drop targets.

`getIsSticky()` is called `repeatedly` while a drop target is no longer being dragged over to
determine whether a drop target should be sticky.

```ts
dropTargetForElements({
	element: myElement,
	getIsSticky: () => true,
});

dropTargetForElements({
	element: myElement,
	getIsSticky: ({ source }: GetFeedbackArgs): boolean => {
		// only be sticky when dragging something with 'author: Alex'
		return source.data.author === 'Alex';
	},
});
```

#### The stickiness algorithm

A _drop target_ that otherwise would not be dragged over any more due to changes in the users
pointer, will continue to be marked as "dragged over" when:

- The drop target is still mounted in the DOM, AND
- The drop target `canDrop()` returns `true` AND
- The drop target `getIsSticky()` returns `true` AND
- The parent of a _drop target_ is unchanged

<SectionMessage appearance='discovery'>

`[]` = Which _drop targets_ are currently dragged over

`[A]` = over the `A` drop target

</SectionMessage>

##### Scenario: `[A(sticky)]` → `[]`

Result: `[A]`

##### Scenario: `[B(sticky), A(sticky)]` → `[]`

Result: `[B, A]`

##### Scenario: `[C, B(sticky), A(sticky)]` → `[]`

Result: `[B, A]`

##### Scenario: `[A(sticky)]` → `[B]`

Result: `[B]`

##### Scenario: `[B(sticky), A]` → `[A]`

Result: `[B, A]`

##### Scenario: `[B, A(sticky)]` → `[A]`

Result: `[A]`

##### Scenario: `[B(sticky), A]` → `[X]`

Result: `[X]`

##### Scenario: `[B(sticky), A]` → `[]`

Result: `[]`

##### Scenario: `[B(sticky), A(sticky)]` → `[X]`

Result: `[X]`

##### Scenario: `[A(sticky)]` → `[]` + `A:canDrop()` returns `false`

Result: `[]`

_Stickiness is not maintained when an old drop target states it cannot be dropped on_

##### Scenario: `[A(sticky)]` → `[]` + `A:getIsSticky()` returns `false`

Result: `[]`

_Stickiness is not maintained when an old drop target states it it is no longer sticky_

##### Scenario: `[A(sticky)]` → `[]` + `A` is unmounted

Result: `[]`

_Stickiness is not maintained when an old drop target is unmounted_

##### Other notes:

- All _drop targets_, regardless of stickiness, will no longer be "dragged over" when the users
  pointer moves out of the `window`.
- Drop targets that are no longer dragged over, but are still active due to stickiness, will _not_
  have their data recomputed with `getData()`, or drop effect recomputed with `getDropEffect()`. The
  last `data` and `dropEffect` created when a drop target is actively being dragged over is
  preserved.

## Nested drop targets

When calculating what _drop targets_ are currently being dragged over, we look from the deepest
possible _drop target_ upwards (bubble ordering). We will search up to the document root to find any
available drop targets. If a _drop target_ specifies that it cannot be dropped on (`canDrop()`
returns `false`), then it will be ignored and the search will continue upwards.

##### Scenario `[] -> [B, A(blocked)]`

Result: `[] -> [B]`

Flow:

- `B` visited and allows dropping. Drop targets: `[] -> [B]`
- `A` visited and does not allow dropping

##### Scenario `[] -> [C, B(blocked), A]`

Result: `[] -> [C, A]`

- Going from no drop targets `[]` to three drop targets: `[C, B, A]` (bubble ordered).
- `C` and `A` allow dropping, but `B` has blocked dropping

Flow:

- `C` visited and allows dropping. Drop targets: `[] -> [C]`
- `B` visited and does not allow dropping
- `A` visited and allows dropping. Drop targets `[C] -> [C, A]`

### Handling `onDrop()` for nested drop targets

When you have nested _drop targets_ all of them will have their `onDrop()` callbacks executed on a
drop. Let's say we have two _drop targets_ `parent` and `child`. You might want to perform one
operation if `parent` is dropped on, and a different operation if `child` is dropped on. In
`parent`s `onDrop()` how do you tell if `child` was dropped on?

We can leverage the fact that `location.current.dropTargets` is always bubbled ordered, so inner
drop targets come before outer ones

```ts
dropTargetForElements({
	element: parent,
	onDrop({ location, self }) {
		// we know that if 'self' is in the first position, then no inner drop target was dropped on
		if (location.current.dropTargets[0]?.element === self.element) {
			action1();
			return;
		}
		// at this point we know that an inner drop target was dropped on
		action2();
	},
});
```

## Related

- [Design guidelines](/components/pragmatic-drag-and-drop/design-guidelines)
