The useDropZone() composable turns an element (or a component) into a target for files dragged from the desktop: it tells you through the reactive isOverDropZone Boolean when a drag hovers the zone, and hands you the dropped File objects through the acceptedDropZoneFiles reactive Array and the onDrop hook.
The dropped files go through the same validation as QFile and QUploader (accept, maxFileSize, maxTotalSize, maxFiles and filter), and the files that do not pass are reported the same way those components emit @rejected.
Use it when you want your own card, panel or whole page to accept dropped files, and QFile’s field design or QUploader’s queue would get in the way. When all you need is the files and a hover class on the element, the v-drop-zone directive does that from the template. Pair it with the useFilePicker composable on a button inside the zone, so that keyboard and touch users can supply the files too.
On the server-side of SSR or SSG modes, nothing can be dropped: isOverDropZone stays false and acceptedDropZoneFiles stays empty until the client takes over.
The composable can also be called outside of setup(): in a boot file, a store or a plain module. There is no component root to fall back on and no mount to wait for, so supply a target (an element, or a ref or getter of one); the zone starts listening right away and nothing releases it by itself: call stopDropZone() (or point a reactive target to null) when you are done.
Syntax
import { useTemplateRef } from 'vue'
import { useDropZone } from 'quasar'
setup () {
const target = useTemplateRef('target') // an Element or a component
const {
isOverDropZone,
acceptedDropZoneFiles,
rejectedDropZoneFiles,
resetDropZone,
stopDropZone
} = useDropZone({
// all optional:
target, // (default: the component's own root element)
disabled: true, // stop accepting drops (default: false)
// validation, same meaning as the QFile/QUploader props:
multiple: true, // accept more than one file per drop (default: false)
accept: 'image/*,.pdf', // same format as the native "accept" attribute (default: any file)
maxFileSize: 1048576, // bytes (default: no limit)
maxTotalSize: 10485760, // bytes (default: no limit)
maxFiles: 5, // (default: no limit)
filter (files) { // keep only the files you return (default: keep them all)
return files.filter(file => file.name.endsWith('.jpg'))
},
onDrop (files, evt) {}, // the accepted files of a drop, plus the Event
onRejected (rejected) {}, // [{ failedPropValidation, file }, ...]
onEnter (evt) {}, // a drag entered the zone
onLeave (evt) {} // it left the zone or got dropped
})
// ...
}function useDropZone(
options?: MaybeRefOrGetter<{
target?: MaybeRefOrGetter<
Element | ComponentPublicInstance | null | undefined
>
disabled?: boolean
multiple?: boolean
accept?: string
maxFileSize?: string | number
maxTotalSize?: string | number
maxFiles?: string | number
filter?: (files: readonly File[]) => readonly File[]
onDrop?: (files: File[], evt: DragEvent) => void
onRejected?: (rejected: QRejectedEntry[]) => void
onEnter?: (evt: DragEvent) => void
onLeave?: (evt: DragEvent) => void
}>
): {
isOverDropZone: Ref<boolean>
acceptedDropZoneFiles: ShallowRef<File[]>
rejectedDropZoneFiles: ShallowRef<QRejectedEntry[]>
resetDropZone: () => void
stopDropZone: () => void
}
// the same type as the entries of the QFile/QUploader "rejected" event
interface QRejectedEntry {
failedPropValidation:
| 'accept'
| 'max-file-size'
| 'max-total-size'
| 'filter'
| 'max-files'
| 'duplicate'
file: File
}Without a target, the zone is the root element of the component the composable is called in, as of the moment the component gets mounted. A component rendering a fragment (multiple root nodes) has no root element to listen on, so supply a target there.
isOverDropZone becomes true while something is being dragged over the zone (its children included) and goes back to false when the drag leaves it or gets dropped; onEnter and onLeave are called on those two transitions, with the drag Event. Use it to highlight the zone. Releasing the zone in the middle of a drag (through disabled, a target swap or stopDropZone()) puts isOverDropZone back to false without calling onLeave, as there is no drag Event to report. A zone nested inside another zone keeps the drag events to itself, so the outer zone does not see a drop made on the inner one (and stays highlighted until the next drag leaves it).
Without multiple, only the first dropped file is kept (the others are not reported as rejected), the same as with QFile. Dropped folders are not opened: they show up as files without a type, which an accept filters out.
acceptedDropZoneFiles holds the accepted files of the latest drop. A drop where every file gets rejected leaves acceptedDropZoneFiles unchanged; rejectedDropZoneFiles always reflects the latest drop. resetDropZone() empties both.
onDrop is called on every drop with the accepted files (an empty Array when nothing passed, or when the drag carried no files at all) and the drop Event, so the other payloads of the drag (evt.dataTransfer.getData('text/plain'), for example) stay within reach. onRejected is called only when at least one file got rejected.
The failedPropValidation of a rejected entry is one of accept, max-file-size, max-total-size, max-files or filter, naming the option that the file did not pass (duplicate belongs to the same QRejectedEntry type, but only QFile and QUploader can report it, when appending to a list).
stopDropZone() releases the zone: the element no longer accepts drops (the browser’s default handling applies again) and isOverDropZone goes back to false. The composable keeps following the options, so pointing target to another element re-arms it; changing any other option does not. It is especially useful when you have not specified a target, since the component’s own root element cannot be swapped out otherwise (with a reactive target, setting it to null releases the zone just the same). There is no need to call it on your component’s destruction, as the composable releases the zone by itself.
A file dropped anywhere else on the page makes the browser navigate to it (or download it), leaving your app. Whether to guard against that is your call: listening to dragover and drop on window and calling preventDefault() on both cancels the navigation everywhere.
Changing the options while running
The options can be a plain Object, a Ref or a getter Function. The validation options and the hooks are read at each drop. With a Ref or a getter, the composable also tracks whatever reactive state target and disabled read and re-applies them whenever that state changes, so you never call anything to “update” it:
- toggling
disabledreleases the zone (isOverDropZonegoes back tofalse, and the browser’s default handling of a drop applies again) and re-arms it - pointing
targetto another element (or letting a template ref change throughv-if) follows it
import { ref } from 'vue'
import { useDropZone } from 'quasar'
setup () {
const uploading = ref(false)
const allowVideos = ref(false)
const { isOverDropZone, acceptedDropZoneFiles } = useDropZone(() => ({
multiple: true,
disabled: uploading.value,
accept: allowVideos.value ? 'image/*,video/*' : 'image/*'
}))
// ...
}Example
The dashed box below accepts dropped images and lights up while a drag hovers it. The button inside opens the file dialog through useFilePicker with the same validation options, and both feed the same list; the rejected files are reported through notifications:
To preview a dropped image (or hand any dropped file to an <img>, a <video> or a download link), pair it with the useObjectUrl composable, which creates the object URL for the File and revokes it when it is no longer needed.