rc

DropZone

A drop zone is an area into which one or multiple objects can be dragged and dropped.

installyarn add @react-spectrum/dropzone
version3.0.0-rc.2
usageimport {DropZone} from '@react-spectrum/dropzone'

Example#


import Upload from '@spectrum-icons/illustrations/Upload';
import {Text} from 'react-aria-components';

function Example() {
  let [isFilled, setIsFilled] = React.useState(false);

  return (
    <>
      <Draggable />
      <DropZone
        maxWidth="size-3000"
        isFilled={isFilled}
        onDrop={() => setIsFilled(true)}>
        <IllustratedMessage>
          <Upload />
          <Heading>
            <Text slot="label">
              {isFilled ? 'You dropped something!' : 'Drag and drop your file'}
            </Text>
          </Heading>
        </IllustratedMessage>
      </DropZone>
    </>
  )
}
import Upload from '@spectrum-icons/illustrations/Upload';
import {Text} from 'react-aria-components';

function Example() {
  let [isFilled, setIsFilled] = React.useState(false);

  return (
    <>
      <Draggable />
      <DropZone
        maxWidth="size-3000"
        isFilled={isFilled}
        onDrop={() => setIsFilled(true)}
      >
        <IllustratedMessage>
          <Upload />
          <Heading>
            <Text slot="label">
              {isFilled
                ? 'You dropped something!'
                : 'Drag and drop your file'}
            </Text>
          </Heading>
        </IllustratedMessage>
      </DropZone>
    </>
  );
}
import Upload from '@spectrum-icons/illustrations/Upload';
import {Text} from 'react-aria-components';

function Example() {
  let [
    isFilled,
    setIsFilled
  ] = React.useState(
    false
  );

  return (
    <>
      <Draggable />
      <DropZone
        maxWidth="size-3000"
        isFilled={isFilled}
        onDrop={() =>
          setIsFilled(
            true
          )}
      >
        <IllustratedMessage>
          <Upload />
          <Heading>
            <Text slot="label">
              {isFilled
                ? 'You dropped something!'
                : 'Drag and drop your file'}
            </Text>
          </Heading>
        </IllustratedMessage>
      </DropZone>
    </>
  );
}

The Draggable component used above is defined below. See useDrag for more details and documentation.

Show code
import {useDrag} from '@react-aria/dnd';

function Draggable() {
  let { dragProps, isDragging } = useDrag({
    getItems() {
      return [{
        'text/plain': 'hello world',
        'my-app-custom-type': JSON.stringify({ message: 'hello world' })
      }];
    }
  });

  return (
    <div
      {...dragProps}
      role="button"
      tabIndex={0}
      className={`draggable ${isDragging ? 'dragging' : ''}`}
    >
      Drag me
    </div>
  );
}
import {useDrag} from '@react-aria/dnd';

function Draggable() {
  let { dragProps, isDragging } = useDrag({
    getItems() {
      return [{
        'text/plain': 'hello world',
        'my-app-custom-type': JSON.stringify({
          message: 'hello world'
        })
      }];
    }
  });

  return (
    <div
      {...dragProps}
      role="button"
      tabIndex={0}
      className={`draggable ${
        isDragging ? 'dragging' : ''
      }`}
    >
      Drag me
    </div>
  );
}
import {useDrag} from '@react-aria/dnd';

function Draggable() {
  let {
    dragProps,
    isDragging
  } = useDrag({
    getItems() {
      return [{
        'text/plain':
          'hello world',
        'my-app-custom-type':
          JSON.stringify(
            {
              message:
                'hello world'
            }
          )
      }];
    }
  });

  return (
    <div
      {...dragProps}
      role="button"
      tabIndex={0}
      className={`draggable ${
        isDragging
          ? 'dragging'
          : ''
      }`}
    >
      Drag me
    </div>
  );
}
Show CSS
.draggable {
  display: inline-block;
  vertical-align: top;
  border: 1px solid gray;
  padding: 10px;
  margin-right: 20px;
  margin-bottom: 20px;
  border-radius: 4px;
  height: fit-content;
}

.draggable.dragging {
  opacity: 0.5;
}
.draggable {
  display: inline-block;
  vertical-align: top;
  border: 1px solid gray;
  padding: 10px;
  margin-right: 20px;
  margin-bottom: 20px;
  border-radius: 4px;
  height: fit-content;
}

.draggable.dragging {
  opacity: 0.5;
}
.draggable {
  display: inline-block;
  vertical-align: top;
  border: 1px solid gray;
  padding: 10px;
  margin-right: 20px;
  margin-bottom: 20px;
  border-radius: 4px;
  height: fit-content;
}

.draggable.dragging {
  opacity: 0.5;
}

Content#


A DropZone accepts an IllustratedMessage as a child which is comprised of three areas: an illustration, a title, and a body. Each of these sections can be populated by providing the following components to the IllustratedMessage as children: a SVG, a Heading (title), and a Content (body). A FileTrigger is commonly paired with a DropZone to allow a user to choose files from their device.

import {FileTrigger} from 'react-aria-components';
import {Text} from 'react-aria-components';

function Example() {
  let [isFilled, setIsFilled] = React.useState(false);

  return (
    <>
      <Draggable />
      <DropZone
        maxWidth="size-3000"
        isFilled={isFilled}
        onDrop={() => setIsFilled(true)}>
        <IllustratedMessage>
          <Upload />
          <Heading>
            <Text slot="label">
              {isFilled ? 'You dropped something!' : 'Drag and drop here'}
            </Text>
          </Heading>
          <Content>
            <FileTrigger
              onSelect={()=> setIsFilled(true)}>
              <Button variant="primary">Browse</Button>
            </FileTrigger>
          </Content>
        </IllustratedMessage>
      </DropZone>
    </>
  )
}
import {FileTrigger} from 'react-aria-components';
import {Text} from 'react-aria-components';

function Example() {
  let [isFilled, setIsFilled] = React.useState(false);

  return (
    <>
      <Draggable />
      <DropZone
        maxWidth="size-3000"
        isFilled={isFilled}
        onDrop={() => setIsFilled(true)}
      >
        <IllustratedMessage>
          <Upload />
          <Heading>
            <Text slot="label">
              {isFilled
                ? 'You dropped something!'
                : 'Drag and drop here'}
            </Text>
          </Heading>
          <Content>
            <FileTrigger
              onSelect={() => setIsFilled(true)}
            >
              <Button variant="primary">Browse</Button>
            </FileTrigger>
          </Content>
        </IllustratedMessage>
      </DropZone>
    </>
  );
}
import {FileTrigger} from 'react-aria-components';
import {Text} from 'react-aria-components';

function Example() {
  let [
    isFilled,
    setIsFilled
  ] = React.useState(
    false
  );

  return (
    <>
      <Draggable />
      <DropZone
        maxWidth="size-3000"
        isFilled={isFilled}
        onDrop={() =>
          setIsFilled(
            true
          )}
      >
        <IllustratedMessage>
          <Upload />
          <Heading>
            <Text slot="label">
              {isFilled
                ? 'You dropped something!'
                : 'Drag and drop here'}
            </Text>
          </Heading>
          <Content>
            <FileTrigger
              onSelect={() =>
                setIsFilled(
                  true
                )}
            >
              <Button variant="primary">
                Browse
              </Button>
            </FileTrigger>
          </Content>
        </IllustratedMessage>
      </DropZone>
    </>
  );
}

Accessibility#

A visual label should be provided to DropZone using a Text element with a label slot. If it is not provided, then an aria-label or aria-labelledby prop must be passed to identify the visually hidden button to assistive technology.

Internationalization#

In order to internationalize a DropZone, a localized string should be passed to the Text element with a label slot or to the aria-label prop, in addition to the replaceMessage prop.

Events#


DropZone supports drop operations via mouse, keyboard, and touch. You can handle all of these via the onDrop prop. In addition, the onDropEnter, onDropMove, and onDropExit events are fired as the user enter and exists the dropzone during a drag operation.

The following example uses an onDrop handler to update the filled status stored in React state.

import File from '@spectrum-icons/illustrations/File';
import {Text} from 'react-aria-components';
import {Flex} from '@react-spectrum/layout';

function Example() {
  let [filledSrc, setFilledSrc] = React.useState(null);

  return (
    <>
      <Draggable />
      <DropZone
        maxWidth="size-3000"
        isFilled={!!filledSrc}
        onDrop={async (e) => {
          e.items.find(async (item) => {
            if (item.kind === 'file') {
              setFilledSrc(item.name);
            } else if (item.kind === 'text' && item.types.has('text/plain')) {
              setFilledSrc(await item.getText('text/plain'));
            }
          });
        }}
      >
        {filledSrc
          ? (
            <Flex
              direction="column"
              alignItems="center"
              justifyContent="center"
              gap="size-100"
            >
              <File />
              {filledSrc}
            </Flex>
          )
          : (
            <IllustratedMessage>
              <Upload />
              <Heading>
                <Text slot="label">
                  Drag and drop here
                </Text>
              </Heading>
            </IllustratedMessage>
          )}
      </DropZone>
    </>
  );
}
import File from '@spectrum-icons/illustrations/File';
import {Text} from 'react-aria-components';
import {Flex} from '@react-spectrum/layout';

function Example() {
  let [filledSrc, setFilledSrc] = React.useState(null);

  return (
    <>
      <Draggable />
      <DropZone
        maxWidth="size-3000"
        isFilled={!!filledSrc}
        onDrop={async (e) => {
          e.items.find(async (item) => {
            if (item.kind === 'file') {
              setFilledSrc(item.name);
            } else if (
              item.kind === 'text' &&
              item.types.has('text/plain')
            ) {
              setFilledSrc(
                await item.getText('text/plain')
              );
            }
          });
        }}
      >
        {filledSrc
          ? (
            <Flex
              direction="column"
              alignItems="center"
              justifyContent="center"
              gap="size-100"
            >
              <File />
              {filledSrc}
            </Flex>
          )
          : (
            <IllustratedMessage>
              <Upload />
              <Heading>
                <Text slot="label">
                  Drag and drop here
                </Text>
              </Heading>
            </IllustratedMessage>
          )}
      </DropZone>
    </>
  );
}
import File from '@spectrum-icons/illustrations/File';
import {Text} from 'react-aria-components';
import {Flex} from '@react-spectrum/layout';

function Example() {
  let [
    filledSrc,
    setFilledSrc
  ] = React.useState(
    null
  );

  return (
    <>
      <Draggable />
      <DropZone
        maxWidth="size-3000"
        isFilled={!!filledSrc}
        onDrop={async (
          e
        ) => {
          e.items.find(
            async (
              item
            ) => {
              if (
                item
                  .kind ===
                  'file'
              ) {
                setFilledSrc(
                  item
                    .name
                );
              } else if (
                item
                    .kind ===
                  'text' &&
                item
                  .types
                  .has(
                    'text/plain'
                  )
              ) {
                setFilledSrc(
                  await item
                    .getText(
                      'text/plain'
                    )
                );
              }
            }
          );
        }}
      >
        {filledSrc
          ? (
            <Flex
              direction="column"
              alignItems="center"
              justifyContent="center"
              gap="size-100"
            >
              <File />
              {filledSrc}
            </Flex>
          )
          : (
            <IllustratedMessage>
              <Upload />
              <Heading>
                <Text slot="label">
                  Drag
                  and
                  drop
                  here
                </Text>
              </Heading>
            </IllustratedMessage>
          )}
      </DropZone>
    </>
  );
}

Props#


NameTypeDescription
childrenReactNodeThe content to display in the drop zone.
isFilledbooleanWhether the drop zone has been filled.
replaceMessagestringThe message to replace the default banner message that is shown when the drop zone is filled.
getDropOperation( (types: DragTypes, , allowedOperations: DropOperation[] )) => DropOperation

A function returning the drop operation to be performed when items matching the given types are dropped on the drop target.

classNamestring( (values: DropZoneRenderProps )) => stringThe CSS className for the element. A function may be provided to compute the class based on component state.
styleCSSProperties( (values: DropZoneRenderProps )) => CSSPropertiesThe inline style for the element. A function may be provided to compute the style based on component state.
Events
NameTypeDescription
onDropEnter( (e: DropEnterEvent )) => voidHandler that is called when a valid drag enters the drop target.
onDropMove( (e: DropMoveEvent )) => voidHandler that is called when a valid drag is moved within the drop target.
onDropExit( (e: DropExitEvent )) => voidHandler that is called when a valid drag exits the drop target.
onDrop( (e: DropEvent )) => voidHandler that is called when a valid drag is dropped on the drop target.
Layout
NameTypeDescription
flexResponsive<stringnumberboolean>When used in a flex layout, specifies how the element will grow or shrink to fit the space available. See MDN.
flexGrowResponsive<number>When used in a flex layout, specifies how the element will grow to fit the space available. See MDN.
flexShrinkResponsive<number>When used in a flex layout, specifies how the element will shrink to fit the space available. See MDN.
flexBasisResponsive<numberstring>When used in a flex layout, specifies the initial main size of the element. See MDN.
alignSelfResponsive<'auto''normal''start''end''center''flex-start''flex-end''self-start''self-end''stretch'>Overrides the alignItems property of a flex or grid container. See MDN.
justifySelfResponsive<'auto''normal''start''end''flex-start''flex-end''self-start''self-end''center''left''right''stretch'>Specifies how the element is justified inside a flex or grid container. See MDN.
orderResponsive<number>The layout order for the element within a flex or grid container. See MDN.
gridAreaResponsive<string>When used in a grid layout, specifies the named grid area that the element should be placed in within the grid. See MDN.
gridColumnResponsive<string>When used in a grid layout, specifies the column the element should be placed in within the grid. See MDN.
gridRowResponsive<string>When used in a grid layout, specifies the row the element should be placed in within the grid. See MDN.
gridColumnStartResponsive<string>When used in a grid layout, specifies the starting column to span within the grid. See MDN.
gridColumnEndResponsive<string>When used in a grid layout, specifies the ending column to span within the grid. See MDN.
gridRowStartResponsive<string>When used in a grid layout, specifies the starting row to span within the grid. See MDN.
gridRowEndResponsive<string>When used in a grid layout, specifies the ending row to span within the grid. See MDN.
slotstringnull

A slot name for the component. Slots allow the component to receive props from a parent component. An explicit null value indicates that the local props completely override all props received from a parent.

Spacing
NameTypeDescription
marginResponsive<DimensionValue>The margin for all four sides of the element. See MDN.
marginTopResponsive<DimensionValue>The margin for the top side of the element. See MDN.
marginBottomResponsive<DimensionValue>The margin for the bottom side of the element. See MDN.
marginStartResponsive<DimensionValue>The margin for the logical start side of the element, depending on layout direction. See MDN.
marginEndResponsive<DimensionValue>The margin for the logical end side of an element, depending on layout direction. See MDN.
marginXResponsive<DimensionValue>The margin for both the left and right sides of the element. See MDN.
marginYResponsive<DimensionValue>The margin for both the top and bottom sides of the element. See MDN.
Sizing
NameTypeDescription
widthResponsive<DimensionValue>The width of the element. See MDN.
minWidthResponsive<DimensionValue>The minimum width of the element. See MDN.
maxWidthResponsive<DimensionValue>The maximum width of the element. See MDN.
heightResponsive<DimensionValue>The height of the element. See MDN.
minHeightResponsive<DimensionValue>The minimum height of the element. See MDN.
maxHeightResponsive<DimensionValue>The maximum height of the element. See MDN.
Positioning
NameTypeDescription
positionResponsive<'static''relative''absolute''fixed''sticky'>Specifies how the element is positioned. See MDN.
topResponsive<DimensionValue>The top position for the element. See MDN.
bottomResponsive<DimensionValue>The bottom position for the element. See MDN.
leftResponsive<DimensionValue>The left position for the element. See MDN. Consider using start instead for RTL support.
rightResponsive<DimensionValue>The right position for the element. See MDN. Consider using start instead for RTL support.
startResponsive<DimensionValue>The logical start position for the element, depending on layout direction. See MDN.
endResponsive<DimensionValue>The logical end position for the element, depending on layout direction. See MDN.
zIndexResponsive<number>The stacking order for the element. See MDN.
isHiddenResponsive<boolean>Hides the element.
Accessibility
NameTypeDescription
idstringThe element's unique identifier. See MDN.
aria-labelstringDefines a string value that labels the current element.
aria-labelledbystringIdentifies the element (or elements) that labels the current element.
aria-describedbystringIdentifies the element (or elements) that describes the object.
aria-detailsstringIdentifies the element (or elements) that provide a detailed, extended description for the object.
Advanced
NameTypeDescription
UNSAFE_classNamestringSets the CSS className for the element. Only use as a last resort. Use style props instead.
UNSAFE_styleCSSPropertiesSets inline style for the element. Only use as a last resort. Use style props instead.

Visual options#


Filled state#

The user is responsible for both managing the filled state of a DropZone and handling the associated styling. To set the DropZone to a filled state, the user must pass the isFilled prop.

The example below demonstrates one way of styling the filled state.

import {Text} from 'react-aria-components';

function Example() {
  let [filledSrc, setFilledSrc] = React.useState(null);

  return (
    <>
      <DraggableImage />
      <DropZone
        isFilled={!!filledSrc}
        maxWidth="size-3000"
        height="size-2400"
        getDropOperation={(types) =>
          (types.has('image/png') || types.has('image/jpeg'))
            ? 'copy'
            : 'cancel'}
        onDrop={async (e) => {
          e.items.find(async (item) => {
            if (item.kind === 'file') {
              if (item.type === 'image/jpeg' || item.type === 'image/png') {
                setFilledSrc(URL.createObjectURL(await item.getFile()));
              }
            } else if (item.kind === 'text') {
              setFilledSrc(await item.getText('image/jpeg'));
            }
          });
        }}
      >
        {filledSrc
          ? <img className="images" alt="" src={filledSrc} />
          : (
            <IllustratedMessage>
              <Upload />
              <Heading>
                <Text slot="label">
                  Drag and drop photos
                </Text>
              </Heading>
            </IllustratedMessage>
          )}
      </DropZone>
    </>
  );
}
import {Text} from 'react-aria-components';

function Example() {
  let [filledSrc, setFilledSrc] = React.useState(null);

  return (
    <>
      <DraggableImage />
      <DropZone
        isFilled={!!filledSrc}
        maxWidth="size-3000"
        height="size-2400"
        getDropOperation={(types) =>
          (types.has('image/png') ||
              types.has('image/jpeg'))
            ? 'copy'
            : 'cancel'}
        onDrop={async (e) => {
          e.items.find(async (item) => {
            if (item.kind === 'file') {
              if (
                item.type === 'image/jpeg' ||
                item.type === 'image/png'
              ) {
                setFilledSrc(
                  URL.createObjectURL(await item.getFile())
                );
              }
            } else if (item.kind === 'text') {
              setFilledSrc(
                await item.getText('image/jpeg')
              );
            }
          });
        }}
      >
        {filledSrc
          ? (
            <img
              className="images"
              alt=""
              src={filledSrc}
            />
          )
          : (
            <IllustratedMessage>
              <Upload />
              <Heading>
                <Text slot="label">
                  Drag and drop photos
                </Text>
              </Heading>
            </IllustratedMessage>
          )}
      </DropZone>
    </>
  );
}
import {Text} from 'react-aria-components';

function Example() {
  let [
    filledSrc,
    setFilledSrc
  ] = React.useState(
    null
  );

  return (
    <>
      <DraggableImage />
      <DropZone
        isFilled={!!filledSrc}
        maxWidth="size-3000"
        height="size-2400"
        getDropOperation={(
          types
        ) =>
          (types.has(
              'image/png'
            ) ||
              types.has(
                'image/jpeg'
              ))
            ? 'copy'
            : 'cancel'}
        onDrop={async (
          e
        ) => {
          e.items.find(
            async (
              item
            ) => {
              if (
                item
                  .kind ===
                  'file'
              ) {
                if (
                  item
                      .type ===
                    'image/jpeg' ||
                  item
                      .type ===
                    'image/png'
                ) {
                  setFilledSrc(
                    URL
                      .createObjectURL(
                        await item
                          .getFile()
                      )
                  );
                }
              } else if (
                item
                  .kind ===
                  'text'
              ) {
                setFilledSrc(
                  await item
                    .getText(
                      'image/jpeg'
                    )
                );
              }
            }
          );
        }}
      >
        {filledSrc
          ? (
            <img
              className="images"
              alt=""
              src={filledSrc}
            />
          )
          : (
            <IllustratedMessage>
              <Upload />
              <Heading>
                <Text slot="label">
                  Drag
                  and
                  drop
                  photos
                </Text>
              </Heading>
            </IllustratedMessage>
          )}
      </DropZone>
    </>
  );
}
Show CSS
.images {
  position: absolute;
  top: 0px;
  left: 0px;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--spectrum-alias-border-radius-small);
}
.images {
  position: absolute;
  top: 0px;
  left: 0px;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--spectrum-alias-border-radius-small);
}
.images {
  position: absolute;
  top: 0px;
  left: 0px;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--spectrum-alias-border-radius-small);
}

The DraggableImage component used above is defined below. See useDrag for more details and documentation.

Show code
function DraggableImage() {
  let {dragProps, isDragging} = useDrag({
    getItems() {
      return [
        {
          'image/jpeg': 'https://i.imgur.com/Z7AzH2c.jpg'
        }
      ];
    }
  });

  return (
    <div
      {...dragProps}
      role="button"
      tabIndex={0}
      className={`draggable ${isDragging ? 'dragging' : ''}`} >
      <img
        width="150px"
        height="100px"
        alt="Traditional Roof"
        src="https://i.imgur.com/Z7AzH2c.jpg"/>
    </div>
  );
}
function DraggableImage() {
  let { dragProps, isDragging } = useDrag({
    getItems() {
      return [
        {
          'image/jpeg': 'https://i.imgur.com/Z7AzH2c.jpg'
        }
      ];
    }
  });

  return (
    <div
      {...dragProps}
      role="button"
      tabIndex={0}
      className={`draggable ${
        isDragging ? 'dragging' : ''
      }`}
    >
      <img
        width="150px"
        height="100px"
        alt="Traditional Roof"
        src="https://i.imgur.com/Z7AzH2c.jpg"
      />
    </div>
  );
}
function DraggableImage() {
  let {
    dragProps,
    isDragging
  } = useDrag({
    getItems() {
      return [
        {
          'image/jpeg':
            'https://i.imgur.com/Z7AzH2c.jpg'
        }
      ];
    }
  });

  return (
    <div
      {...dragProps}
      role="button"
      tabIndex={0}
      className={`draggable ${
        isDragging
          ? 'dragging'
          : ''
      }`}
    >
      <img
        width="150px"
        height="100px"
        alt="Traditional Roof"
        src="https://i.imgur.com/Z7AzH2c.jpg"
      />
    </div>
  );
}

Replace message#

When a DropZone is in a filled state and has an object dragged over it, a message will appear in front of the DropZone. By default, this message will say "Drop file to replace". However, users can choose to customize this message through the replaceMessage prop. This message should describe the interaction that will occur when the object is dropped. It should also be internationalized if needed.

import {Text} from 'react-aria-components';

function Example() {
  let [isFilled, setIsFilled] = React.useState(false);

  return (
    <>
      <Draggable />
      <DropZone
        isFilled={isFilled}
        maxWidth="size-3000"
        replaceMessage="This is a custom message"
        onDrop={() => setIsFilled(true)}>
        <IllustratedMessage>
          <Upload />
          <Heading>
            <Text slot="label">
              {isFilled ? 'You dropped something!' : 'Drag and drop here'}
            </Text>
          </Heading>
        </IllustratedMessage>
      </DropZone>
    </>
  );
}
import {Text} from 'react-aria-components';

function Example() {
  let [isFilled, setIsFilled] = React.useState(false);

  return (
    <>
      <Draggable />
      <DropZone
        isFilled={isFilled}
        maxWidth="size-3000"
        replaceMessage="This is a custom message"
        onDrop={() => setIsFilled(true)}
      >
        <IllustratedMessage>
          <Upload />
          <Heading>
            <Text slot="label">
              {isFilled
                ? 'You dropped something!'
                : 'Drag and drop here'}
            </Text>
          </Heading>
        </IllustratedMessage>
      </DropZone>
    </>
  );
}
import {Text} from 'react-aria-components';

function Example() {
  let [
    isFilled,
    setIsFilled
  ] = React.useState(
    false
  );

  return (
    <>
      <Draggable />
      <DropZone
        isFilled={isFilled}
        maxWidth="size-3000"
        replaceMessage="This is a custom message"
        onDrop={() =>
          setIsFilled(
            true
          )}
      >
        <IllustratedMessage>
          <Upload />
          <Heading>
            <Text slot="label">
              {isFilled
                ? 'You dropped something!'
                : 'Drag and drop here'}
            </Text>
          </Heading>
        </IllustratedMessage>
      </DropZone>
    </>
  );
}

Visual feedback#

A DropZone displays visual feedback to the user when a drag hovers over the drop target by passing the getDropOperation function. If a drop target only supports data of specific types (e.g. images, videos, text, etc.), then it should implement the getDropOperation prop and return 'cancel' for types that aren't supported. This will prevent visual feedback indicating that the drop target accepts the dragged data when this is not true. Read more about getDropOperation.

In the below example, the drop zone only supports dropping JPEG images. If a JPEG is dragged over the drop zone, it will be highlighted and the operating system will display a copy cursor. If another type is dragged over the drop zone, then there is no visual feedback, indicating that a drop is not accepted.

import {FileTrigger} from 'react-aria-components';
import {Text} from 'react-aria-components';

function Example() {
  let [filledSrc, setFilledSrc] = React.useState(null);

  return (
    <>
      <Draggable />
      <DraggableImage />
      <DropZone
        maxWidth="size-3000"
        isFilled={!!filledSrc}
        getDropOperation={(types) =>
          types.has('image/jpeg') ? 'copy' : 'cancel'}
        onDrop={async (e) => {
          e.items.find(async (item) => {
            if (item.kind === 'file') {
              if (item.type === 'image/jpeg') {
                let file = await item.getFile();
                setFilledSrc({
                  type: file.type,
                  name: file.name
                });
              }
            } else if (item.kind === 'text') {
              let file = await item.getText('image/jpeg');
              setFilledSrc({
                type: 'image/jpeg',
                name: file
              });
            }
          });
        }}
      >
        <IllustratedMessage>
          <Upload />
          <Heading>
            <Text slot="label">
              {filledSrc
                ? `${filledSrc.type} ${filledSrc.name}`
                : 'Drag and drop here'}
            </Text>
          </Heading>
          <Content>
            <FileTrigger
              acceptedFileTypes={['image/jpeg']}
              onSelect={(e) => {
                let file = (Array.from(e)).find((file) =>
                  file.type === 'image/jpeg'
                );
                if (file) {
                  setFilledSrc({
                    type: file.type,
                    name: file.name
                  });
                }
              }}
            >
              <Button variant="primary">Browse</Button>
            </FileTrigger>
          </Content>
        </IllustratedMessage>
      </DropZone>
    </>
  );
}
import {FileTrigger} from 'react-aria-components';
import {Text} from 'react-aria-components';

function Example() {
  let [filledSrc, setFilledSrc] = React.useState(null);

  return (
    <>
      <Draggable />
      <DraggableImage />
      <DropZone
        maxWidth="size-3000"
        isFilled={!!filledSrc}
        getDropOperation={(types) =>
          types.has('image/jpeg') ? 'copy' : 'cancel'}
        onDrop={async (e) => {
          e.items.find(async (item) => {
            if (item.kind === 'file') {
              if (item.type === 'image/jpeg') {
                let file = await item.getFile();
                setFilledSrc({
                  type: file.type,
                  name: file.name
                });
              }
            } else if (item.kind === 'text') {
              let file = await item.getText('image/jpeg');
              setFilledSrc({
                type: 'image/jpeg',
                name: file
              });
            }
          });
        }}
      >
        <IllustratedMessage>
          <Upload />
          <Heading>
            <Text slot="label">
              {filledSrc
                ? `${filledSrc.type} ${filledSrc.name}`
                : 'Drag and drop here'}
            </Text>
          </Heading>
          <Content>
            <FileTrigger
              acceptedFileTypes={['image/jpeg']}
              onSelect={(e) => {
                let file = (Array.from(e)).find((file) =>
                  file.type === 'image/jpeg'
                );
                if (file) {
                  setFilledSrc({
                    type: file.type,
                    name: file.name
                  });
                }
              }}
            >
              <Button variant="primary">Browse</Button>
            </FileTrigger>
          </Content>
        </IllustratedMessage>
      </DropZone>
    </>
  );
}
import {FileTrigger} from 'react-aria-components';
import {Text} from 'react-aria-components';

function Example() {
  let [
    filledSrc,
    setFilledSrc
  ] = React.useState(
    null
  );

  return (
    <>
      <Draggable />
      <DraggableImage />
      <DropZone
        maxWidth="size-3000"
        isFilled={!!filledSrc}
        getDropOperation={(
          types
        ) =>
          types.has(
              'image/jpeg'
            )
            ? 'copy'
            : 'cancel'}
        onDrop={async (
          e
        ) => {
          e.items.find(
            async (
              item
            ) => {
              if (
                item
                  .kind ===
                  'file'
              ) {
                if (
                  item
                    .type ===
                    'image/jpeg'
                ) {
                  let file =
                    await item
                      .getFile();
                  setFilledSrc(
                    {
                      type:
                        file
                          .type,
                      name:
                        file
                          .name
                    }
                  );
                }
              } else if (
                item
                  .kind ===
                  'text'
              ) {
                let file =
                  await item
                    .getText(
                      'image/jpeg'
                    );
                setFilledSrc(
                  {
                    type:
                      'image/jpeg',
                    name:
                      file
                  }
                );
              }
            }
          );
        }}
      >
        <IllustratedMessage>
          <Upload />
          <Heading>
            <Text slot="label">
              {filledSrc
                ? `${filledSrc.type} ${filledSrc.name}`
                : 'Drag and drop here'}
            </Text>
          </Heading>
          <Content>
            <FileTrigger
              acceptedFileTypes={[
                'image/jpeg'
              ]}
              onSelect={(
                e
              ) => {
                let file =
                  (Array
                    .from(
                      e
                    )).find(
                      (
                        file
                      ) =>
                        file
                          .type ===
                          'image/jpeg'
                    );
                if (
                  file
                ) {
                  setFilledSrc(
                    {
                      type:
                        file
                          .type,
                      name:
                        file
                          .name
                    }
                  );
                }
              }}
            >
              <Button variant="primary">
                Browse
              </Button>
            </FileTrigger>
          </Content>
        </IllustratedMessage>
      </DropZone>
    </>
  );
}