Skip to content

ComponentsForms

Drop Zone

A drag-and-drop target for files, folders or text, paired with FileTrigger for click-to-browse. Accessible to keyboard and screen-reader users through paste and keyboard drag and drop, with type filtering and a drop-target state for styling.

React AriaSource
Drop files hereor

Installation

pnpm dlx shadcn@latest add @desyne/drop-zone

The CLI installs dependencies and any other components this one uses.

Usage

tsx
import { isFileDropItem, Text } from "react-aria-components";
import { Button } from "@/components/ui/button";
import { DropZone, FileTrigger } from "@/components/ui/drop-zone";
tsx
<DropZone
  onDrop={async (e) => {
    const files = await Promise.all(
      e.items.filter(isFileDropItem).map((item) => item.getFile()),
    );
    upload(files);
  }}
>
  <Text slot="label">Drop files here</Text>
  <FileTrigger allowsMultiple onSelect={(list) => upload(Array.from(list ?? []))}>
    <Button variant="outline" size="sm">Browse files</Button>
  </FileTrigger>
</DropZone>

Always offer a file picker

Dragging isn't possible on many touch devices and is hard for some users. Put a FileTrigger button inside (or next to) every drop zone so files can also be chosen from the system dialog.

DropZone only receives data; it doesn't upload or store anything. onDrop gives you DropItems: use isFileDropItem, isDirectoryDropItem and isTextDropItem from react-aria-components to narrow them, then getFile(), getEntries() or getText(type) to read them.

When to use

  • Drop Zone — uploading files or accepting dragged data into a specific area of the page.
  • FileTrigger alone — a plain "Upload" button where dragging adds nothing, such as a single attachment in a dense form.
  • Grid List or Table with useDragAndDrop — reordering or dropping items between positions in a collection.

Anatomy

tsx
<DropZone>                 {/* drop target + visually hidden button */}
  <Icon />
  <Text slot="label" />    {/* names the drop zone for screen readers */}
  <FileTrigger>            {/* hidden <input type="file"> */}
    <Button />             {/* opens the system file dialog */}
  </FileTrigger>
</DropZone>
PartRendersNotes
DropZone<div> + visually hidden <button>Dashed box, centered column. The hidden button receives focus, paste and keyboard drops.
Text slot="label"<span>From react-aria-components. Its text becomes the accessible name of the hidden button.
FileTriggerhidden <input type="file">Re-exported from React Aria. Makes its child (any pressable, usually a Button) open the file dialog.

Examples

Sizes

size sets the minimum height and padding: sm for inline attachment areas, md (default) for forms, lg for a page's primary upload target.

Drop files here (sm)
Drop files here (md)
Drop files here (lg)

Accepted file types

getDropOperation decides whether a drag is accepted while it's still over the zone. Return "cancel" for unsupported types and the zone won't highlight or accept the drop. Match it with acceptedFileTypes on FileTrigger, and still filter in onDrop, since some browsers don't reveal file types until the drop.

Drop images herePNG, JPEG or WebP

Image previews

Read each FileDropItem with getFile() and preview it with URL.createObjectURL. Revoke the URLs when they're no longer shown.

Drop photos to preview

Drop-target state

children (and className) accept a function of the zone's state. Use isDropTarget to change the message while something is dragged over it.

Drag files over this area0 items received

Text and paste

Drop zones accept any drag data, not only files. Focus the zone (click it or Tab to it) and paste with Ctrl + V / ⌘ + V: the pasted items arrive through the same onDrop, which is how keyboard users can "drop" without dragging.

Drop or paste textClick here, then press Ctrl+V or ⌘V

Folders

A dropped folder arrives as a DirectoryDropItem; iterate getEntries() (recursively) to reach its files. On FileTrigger, acceptDirectory lets users pick a folder instead of files.

Drop a project folder

Disabled

isDisabled stops the zone from accepting drops or paste and dims it. Disable the FileTrigger's button too, and say why.

Storage fullYou've used 5 GB of 5 GB. Upgrade to upload more files.

Validation

Check size and count yourself in onDrop and onSelect, show errors in an aria-live region, and disable the zone once the limit is reached. Link the rules to the zone with aria-describedby.

Drop files or

Up to 3 files, 2 MB each.

Form submission

Files held in state aren't part of the form. Append them to the FormData in onSubmit before sending it.

Drop screenshots or logs

Recipes

Upload list

A compact drop zone with an inline "browse" link, and a list of files with ProgressBars and cancel buttons.

Drag and drop files, or
  • Q3 board deck.pdf4.0 MB

Avatar upload

A circular drop zone around an Avatar that only accepts images, with upload and remove buttons beside it.

Profile photo

Drop an image on the circle, or upload one. Square images work best.

Comment composer

The whole composer is the drop target: a borderless Textarea, attachment chips and a toolbar. Clicking the textarea still focuses it, because the zone only moves focus to its hidden button when you click a non-focusable area.

Accessibility

  • DropZone renders a visually hidden <button> inside the drop area. Clicking a non-interactive part of the zone focuses it, and Tab reaches it like any button.
  • The hidden button is named by a Text slot="label" inside the zone (plus aria-labelledby, if passed), or by aria-label. Without either, it falls back to a generic "DropZone" label, so always provide one.
  • Keyboard and screen-reader users can paste into the focused zone; pasted items are delivered to onDrop with the "copy" operation.
  • During a keyboard or screen-reader drag started from a React Aria draggable (for example a GridList using useDragAndDrop), drop zones become targets that can be reached with Tab and dropped on with Enter.
  • FileTrigger makes its child open the native file dialog, which is fully accessible. Always include one.
  • Announce results (files added, errors) in an aria-live region so users know the drop worked.

Keyboard

KeyAction
TabMoves focus to the drop zone's hidden button, then to the FileTrigger button
Ctrl + V / ⌘ + VPastes clipboard contents into the focused zone
EnterDuring a keyboard drag, drops onto the focused zone
Space / Enter on the buttonOpens the file dialog

Styling

Data attributes

AttributePresent when
data-drop-targetSomething droppable is dragged over the zone (and getDropOperation didn't return "cancel")
data-hoveredHovered with a mouse or pen
data-focused / data-focus-visibleThe hidden button is focused / focused with the keyboard
data-disabledisDisabled is true

By default the drop-target state switches to a solid brand-colored border on an accent background, and keyboard focus draws a ring.

Slots

data-slotElement
drop-zoneRoot

Custom layouts

className is merged with the defaults, so you can reshape the zone entirely: min-h-0 p-0 removes the size padding, items-stretch text-left undoes the centered column, and rounded-full makes a circular target. See the avatar and composer recipes.

Render props

className and children accept a function of the zone state:

tsx
<DropZone className={({ isDropTarget }) => (isDropTarget ? "scale-[1.01]" : "")}>
  {({ isDropTarget }) => (
    <Text slot="label">{isDropTarget ? "Release to upload" : "Drop files here"}</Text>
  )}
</DropZone>

API Reference

DropZone

Prop

Type

Also accepts every prop of React Aria's DropZone.

FileTrigger

Prop

Type

FileTrigger is re-exported unchanged from React Aria. See its props.