Skip to content

DropZone ​

A drop zone takes files. Dropping and picking are the same act to it: a drag over the zone, or a click anywhere in it, both end in one select event carrying File objects. It ships no wording of its own — what the zone says is yours, and it has to follow the multiple you gave it.

ts
import { DropZone, DropZoneTrigger } from "ropav";

Drop files here, or browse

drop-zone-basic.vue
vue
<script setup lang="ts">
import { DropZone, DropZoneTrigger } from "ropav";
import { shallowRef } from "vue";

const taken = shallowRef<string[]>([]);

const onSelect = (files: File[]) => {
  taken.value = files.map((file) => file.name);
};
</script>

<template>
  <div class="stack">
    <DropZone aria-label="Upload files" multiple @select="onSelect">
      <p>Drop files here, or <DropZoneTrigger>browse</DropZoneTrigger></p>
    </DropZone>

    <p v-if="taken.length" class="note">Took: {{ taken.join(", ") }}</p>
  </div>
</template>

<style scoped>
.stack {
  display: flex;
  width: 100%;
  max-width: var(--rp-container-md);
  flex-direction: column;
  gap: calc(var(--rp-spacing) * 3);
}

.note {
  color: var(--rp-muted);
  font-size: var(--rp-text-sm);
  line-height: var(--rp-text-sm--line-height);
}
</style>

select is never emitted empty, and a drop that arrives as a folder is walked, so what you receive is always plain files. DropZoneTrigger is a span, not a button: it names the affordance for a pointer, and a press on it reaches the zone by bubbling.

Filtering what it takes ​

accept is spelled the way the native attribute spells it — a comma-separated list of mime types, type/* wildcards and .ext suffixes. It does three jobs at once: it filters the file picker, it judges a drag while the pointer is still moving, and it filters what a drop emits.

Drop images here, or browse

drop-zone-accept.vue
vue
<script setup lang="ts">
import { DropZone, DropZoneTrigger } from "ropav";
</script>

<template>
  <DropZone accept="image/*" aria-label="Upload images" class="zone" multiple>
    <template #default="{ status }">
      <p v-if="status === 'reject'">Only images can be dropped here</p>
      <p v-else>Drop images here, or <DropZoneTrigger>browse</DropZoneTrigger></p>
    </template>
  </DropZone>
</template>

<style scoped>
.zone {
  width: 100%;
  max-width: var(--rp-container-md);
}
</style>

The status slot prop is idle, accept or reject. A drag is only refused where the refusal is certain: a folder, or a file the platform advertises no mime type for, stays undecided until the drop. A drag advertises types and nothing else, so while one is moving there is no way to know whether it carries one wrong file or several — which is why the refusal above is worded without a count.

Files that arrive despite the filter are dropped silently rather than reported. The browser already filters the picker half the same way, and the component has no error channel of its own to show one in.

Accessibility ​

  • The focusable control is a visually hidden <input type="file">, so the zone is one ordinary tab stop and the keyboard opens the picker the way it activates any file input. aria-label names that input; without one there is nothing to announce.
  • The instructions for an accessible drag-and-drop are attached to the same input, alongside whatever aria-describedby you supply.
  • DropZoneTrigger is deliberately not focusable. A keyboard already reaches this component through the input, and a second stop for the same action would only be one to tab past.

API ​

DropZone

PropTypeDefaultDescription
acceptstring—Which files to take, spelled the way the native accept attribute spells it: a comma separated list of mime types, type/* wildcards and .ext suffixes. It filters the picker, judges a drag while it is still moving, and filters what a drop emits. A drag is only refused where the refusal is certain — see drop-zone-root.vue.
ariaDescribedbystring—
ariaLabelstring—
ariaLabelledbystring—
isDisabledboolean—
multipleboolean—Whether more than one file may arrive at once.

The default slot receives:

Slot propType
isDisabledboolean
isDropTargetboolean
open() => void
statusDropZoneStatus
EventPayload
selectFile[]

DropZoneTrigger

Slots: default.