---
title: v-drop-zone directive
related:
  - title: useDropZone composable
    path: ../vue-composables/use-drop-zone.md
  - title: useFilePicker composable
    path: ../vue-composables/use-file-picker.md
  - title: File Picker
    path: ../vue-components/file.md
  - title: Uploader
    path: ../vue-components/uploader.md
---
"DropZone" is a Quasar directive that turns the DOM element (or component) that it is applied to into a target for files dragged from the desktop, calling a method with the dropped `File` objects and flagging the element with a CSS class while a drag hovers it.

It is the template-side form of the [useDropZone](../vue-composables/use-drop-zone.md) composable. Its Object form validates the dropped files the way [QFile](../vue-components/file.md) does (`accept`, size and count limits, `filter`); the composable adds reactive state, the enter/leave hooks and a target of your choosing, so reach for it when you need any of those.

## DropZone API

### Directive Value

- `value` (Function | object | false, optional)
  Function to call with the dropped files (identical to description of 'handler' prop of the Object form); Object to also validate the dropped files the way QFile does; false or null (any value that is not a handler, in fact) disables the directive, so the element stops accepting drops until a handler is supplied again
  Function signature: `(files: any[], evt: Event) => void`
  Examples:
    - `v-drop-zone="myHandler"`
    - `v-drop-zone="isEnabled ? myHandler : false"`
    - `v-drop-zone="{ handler: myHandler, accept: 'image/*', maxFileSize: 1048576, onRejected: myRejectedHandler }"`
  Params:
    - `files` (any[], required)
      The dropped File objects; only the first one unless the 'multiple' modifier is set; empty when the drag carried no files
      Examples:
        - `[ File, File ]`
    - `evt` (Event, required)
      The drop Event; its dataTransfer holds the other payloads of the drag (text, links)
  Object shape:
    - `handler` (Function, required)
      The handler function to be called with the dropped files
      Function signature: `(files: any[], evt: Event) => void`
      Params:
        - `files` (any[], required)
          The dropped File objects that passed the validation; only the first one unless 'multiple' is set; empty when nothing passed or the drag carried no files
          Examples:
            - `[ File, File ]`
        - `evt` (Event, required)
          The drop Event; its dataTransfer holds the other payloads of the drag (text, links)
    - `multiple` (boolean, optional)
      Hand over every dropped file instead of the first one only; takes precedence over the 'multiple' modifier
    - `accept` (string, optional)
      Comma separated list of unique file type specifiers, the same format as the 'accept' attribute of a native file input
      Examples:
        - `'.jpg, .pdf, image/*'`
        - `'image/jpeg, .pdf'`
    - `maxFileSize` (number | string, optional)
      Maximum size of individual file in bytes
      Examples: `1024`, `'1048576'`
    - `maxTotalSize` (number | string, optional)
      Maximum size of all files combined in bytes
      Examples: `10485760`
    - `maxFiles` (number | string, optional)
      Maximum number of files per drop
      Examples: `5`
    - `filter` (Function, optional)
      Custom filter for the dropped files; only the files you return are handed over
      Function signature: `(files: any[]) => any[]`
      Examples: `files => files.filter(file => file.size === 1024)`
      Params:
        - `files` (any[], required)
          Candidate files
      Returns: `any[]`
        The files that pass
    - `activeClass` (string, optional), default `'q-drop-zone--over'`
      CSS class to apply to the element while a drag hovers it, instead of the default 'q-drop-zone--over'
      Examples: `'my-zone--active'`
    - `onRejected` (Function, optional)
      Called after a drop when some files do not pass the validation (accept, maxFileSize, maxTotalSize, maxFiles, filter)
      Function signature: `(rejectedEntries: any[]) => void`
      Params:
        - `rejectedEntries` (any[], required)
          Array of { failedPropValidation: string, file: File } Objects for files that do not pass the validation

### Directive Modifiers

- `multiple` (boolean, optional)
  Hand over every dropped file instead of the first one only
  Examples: `v-drop-zone.multiple`

## Usage

The directive's value is the handler Function. It takes two parameters: the Array of dropped `File` objects and the drop Event (its `dataTransfer` holds the other payloads of the drag, such as text or links). The handler is called on every drop, with an empty Array when the drag carried no files.

While something is being dragged over the element (its children included), the element carries the `q-drop-zone--over` CSS class, which draws a dashed outline inside the element by default (the same feedback as QFile and QUploader).

> [!TIP]
> A drop zone alone is not reachable by keyboard or touch users. Place a button inside it (or next to it) that opens the file dialog, through [QFile](../vue-components/file.md) or the [useFilePicker](../vue-composables/use-file-picker.md) composable.

### Basic

Only the first dropped file is handed over by default. With the `multiple` modifier (ex: `v-drop-zone.multiple`), the handler receives every dropped file:

```vue
<template>
  <div class="q-gutter-md">
    <div
      v-drop-zone.multiple="onDrop"
      class="drop-zone column flex-center q-pa-lg rounded-borders text-center"
    >
      <q-icon name="cloud_upload" size="48px" />
      <div class="q-mt-sm">Drop files here</div>
    </div>

    <q-list
      v-if="files.length !== 0"
      bordered
      separator
      class="rounded-borders"
    >
      <q-item v-for="(file, index) in files" :key="index">
        <q-item-section>{{ file.name }}</q-item-section>
        <q-item-section side>{{
          format.humanStorageSize(file.size)
        }}</q-item-section>
      </q-item>
    </q-list>
  </div>
</template>

<script setup>
import { shallowRef } from 'vue'
import { format } from 'quasar'

const files = shallowRef([])

function onDrop(dropped) {
  files.value = [...files.value, ...dropped]
}
</script>

<style lang="sass" scoped>
.drop-zone
  border: 1px solid $grey-5
</style>
```

### Styling

The default feedback is a dashed `currentColor` outline. To change it for every drop zone of your app, restyle `.q-drop-zone--over` in your global CSS. For one element, hand over your own class through the `activeClass` option of the Object form instead; the default class (and its outline) is not applied then:

Example "Custom hover style":

```vue
<template>
  <div
    v-drop-zone="{
      handler: onDrop,
      multiple: true,
      activeClass: 'drop-zone--active'
    }"
    class="drop-zone column flex-center q-pa-lg rounded-borders text-center"
  >
    <q-icon name="add_photo_alternate" size="48px" />
    <div class="q-mt-sm">{{ count }} file(s) dropped so far</div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const count = ref(0)

function onDrop(files) {
  count.value += files.length
}
</script>

<style lang="sass" scoped>
.drop-zone
  border: 2px dashed $grey-5
  transition: background-color .2s, border-color .2s

  &--active
    border-color: $primary
    background-color: rgba($primary, .08)
</style>
```

### Validation

The Object form carries the handler next to the validation options of [QFile](../vue-components/file.md): `accept`, `maxFileSize`, `maxTotalSize`, `maxFiles` (per drop) and `filter`, plus `multiple` (which takes precedence over the modifier). Only the files that pass are handed to `handler`; the ones that do not are reported to `onRejected` as `{ failedPropValidation, file }` entries, the same way QFile emits `@rejected`. The options are read at each drop, so an inline Object literal works and can change at runtime:

Example "Validating the dropped files":

```vue
<template>
  <div class="q-gutter-md">
    <div
      v-drop-zone="{
        handler: onDrop,
        multiple: true,
        accept: 'image/*',
        maxFileSize: 1024 * 1024,
        onRejected
      }"
      class="drop-zone column flex-center q-pa-lg rounded-borders text-center"
    >
      <q-icon name="add_photo_alternate" size="48px" />
      <div class="q-mt-sm">Drop images here (up to 1MB each)</div>
    </div>

    <q-list
      v-if="files.length !== 0"
      bordered
      separator
      class="rounded-borders"
    >
      <q-item v-for="(file, index) in files" :key="index">
        <q-item-section>{{ file.name }}</q-item-section>
        <q-item-section side>{{
          format.humanStorageSize(file.size)
        }}</q-item-section>
      </q-item>
    </q-list>
  </div>
</template>

<script setup>
import { shallowRef } from 'vue'
import { format, useQuasar } from 'quasar'

const $q = useQuasar()
const files = shallowRef([])

function onDrop(dropped) {
  files.value = [...files.value, ...dropped]
}

function onRejected(rejected) {
  $q.notify({
    type: 'negative',
    message: `${rejected.length} file(s) did not pass the validation`
  })
}
</script>

<style lang="sass" scoped>
.drop-zone
  border: 1px solid $grey-5
</style>
```

### Disable

Passing in `false` or `null` instead of a Function (any value that is not a handler, in fact, an Object without a `handler` included) disables the directive: the element stops accepting drops (the browser's default handling of a drop applies again) until a handler is supplied again. The DOM element is untouched in the process, so whatever it wraps keeps its state.

```vue
<template>
  <div class="q-gutter-md">
    <q-toggle v-model="enabled" label="Accept drops" />

    <div
      v-drop-zone="enabled ? onDrop : false"
      class="drop-zone column flex-center q-pa-lg rounded-borders text-center"
      :class="{ 'drop-zone--disabled': !enabled }"
    >
      <q-icon name="cloud_upload" size="48px" />
      <div class="q-mt-sm">
        {{ enabled ? 'Drop a file here' : 'Drops are not accepted' }}
      </div>
    </div>

    <div v-if="lastFile !== null">Last dropped file: {{ lastFile.name }}</div>
  </div>
</template>

<script setup>
import { ref, shallowRef } from 'vue'

const enabled = ref(true)
const lastFile = shallowRef(null)

function onDrop(files) {
  lastFile.value = files[0]
}
</script>

<style lang="sass" scoped>
.drop-zone
  border: 1px solid $grey-5
  transition: opacity .2s

  &--disabled
    opacity: .5
</style>
```

> [!WARNING]
> 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.
