dioxus-dnd 3.0.0

Modular, accessible drag-and-drop for Dioxus: sortable lists, kanban boards, trees, grids, file drops, multi-select, touch support and more
Documentation
# File drops

Files dragged in from the operating system: the desktop, a file manager,
another app's window. The payload cannot travel through Rust context here,
because the drag starts outside your app; it arrives inside the browser's
own drag event. `FileDropZone` handles the native ceremony, filters what
arrives, and hands your handler real files.

API reference: [api/file-drops.md](../api/file-drops.md).
Live demo: the
[Upload](https://kindintelligence.github.io/dioxus-dnd/upload) page.

## The mental model

Every other zone in the crate reads the shared drag context. `FileDropZone`
reads nothing from it, which changes three things:

- **No provider, no payload type.** Drop the component anywhere; it needs
  no `DndProvider`. The "payload" is `Vec<FileData>`, Dioxus's platform
  file handle, pulled straight from the native event (`evt.files()`).
- **`data-over` reflects browser events.** Context-backed zones light up
  for pointer, touch and keyboard drags alike; this zone lights up for real
  OS drag events only. An in-app pointer drag can never trigger it, and an
  OS drag can never trigger an in-app `DropZone`.
- **Two callbacks split every drop.** Files that pass the filter reach
  `on_files` as one `FileDrop` batch; files that fail reach `on_rejected`,
  each paired with a `FileRejection` naming the reason. Honest feedback
  costs one match statement.

## A complete example

```rust,ignore
use dioxus::html::FileData;
use dioxus::prelude::*;
use dioxus_dnd::prelude::*;

#[component]
fn Uploader() -> Element {
    rsx! {
        FileDropZone {
            filter: FileFilter::new()
                .content_types(["image/*"])
                .max_size(5_000_000)
                .max_files(6),
            on_files: move |drop: FileDrop| async move {
                for f in drop.files {
                    let bytes = f.read_bytes().await.unwrap(); // web
                    // or: let path = f.path();                // desktop
                }
            },
            on_rejected: move |bad: Vec<(FileData, FileRejection)>| {
                // show each f.name() with its reason
            },
            class: "rounded-xl border-2 border-dashed p-8 data-over:border-blue-500",
            "Drop images here"
        }
    }
}
```

The handler can be async: Dioxus spawns the future for you, so `on_files`
awaits `read_bytes` for each file directly, with no separate effect system
to route reads through.

## Filtering

`FileFilter` is a builder with four rules; a file must pass all of them:

- `extensions(["png", "jpg"])` allow-lists by file name ending.
  Case-insensitive, leading dot optional, whitespace trimmed, and the
  extension must terminate the name: `png.txt` does not pass a `png`
  filter.
- `content_types(["image/*"])` allow-lists by the reported MIME type.
  Patterns cover exact types (`application/pdf`), top-level wildcards
  (`image/*`), all typed files (`*/*`), and structured suffix wildcards
  (`application/*+json`, `*/*+json`). Matching ignores case and MIME
  parameters such as `; charset=utf-8`, and wildcards match whole
  slash-delimited parts, so `imageevil/png` never sneaks past `image/*`.
- `max_size(bytes)` rejects files larger than the cap.
- `max_files(n)` accepts at most `n` files per drop.

Rejections do not consume slots: a file bounced on type does not count
against `max_files`, so valid files behind it still land. An omitted
`filter` prop, or an empty `FileFilter::new()`, accepts everything.

## A UX affordance, not a security boundary

The filter matches on the browser- and OS-reported name, content type and
size, all of which are attacker-controllable. A renamed executable can be
called `photo.png` and report `image/png`, and the size is self-reported.
Use the filter to bounce obviously wrong drops early with a clear reason;
validate the actual bytes server-side, or by content sniffing, before
trusting a file.

## Reading what landed

`FileDrop.files` holds `FileData` values, Dioxus's platform file handle.
Metadata (`name()`, `size()`, `content_type()`, `last_modified()`) is
available everywhere; the contents differ by renderer:

- **Web** has no filesystem paths. Read contents with `read_bytes()`,
  `read_string()` or the chunked `byte_stream()`.
- **Desktop** exposes the real path through `path()`, so you can hand the
  file to `std::fs` or another process without copying its bytes through
  the webview.

The same handler compiles for both; the `FileDrop` also carries `client`
and `element` drop coordinates when placement matters.

## Styling the zone

While an OS drag hovers, the wrapper div carries `data-over="true"` and
drops it otherwise, so the classic highlight needs no handler: Tailwind
`data-over:border-blue-500`, plain CSS `[data-over]`. An enter/leave depth
counter keeps the attribute stable while the drag crosses the zone's
children, so nested markup does not make it flicker. When styling needs
real state, `on_hover` fires `true` on enter and `false` on leave or drop.

The zone is fully headless: borders, tints and result chips in the demo
are all page styling over `data-over` and the two callbacks.

## Gotchas

- **A missing MIME type fails any type filter.** A file that reports no
  content type is rejected by every `content_types` filter, including
  `*/*`. Filter by extension instead when your sources report types
  unreliably.
- **Suffix wildcards do not cover the bare type.** `application/*+json`
  matches `application/ld+json` but not `application/json`; list both when
  you want both. Subtype wildcards without a suffix (`*/json`) are not
  supported and match nothing.
- **Empty drops are silent.** A drag that delivers no files (dragged text,
  for example) fires neither callback. Text and links from other apps are
  a different concept: [External content in]external-content.md.
- **Windows desktop file drops have webview quirks.** wry-based webviews
  have a history of platform issues here; test on your target and consider
  a file input fallback. wry also makes its drop handler and HTML5
  drag-and-drop mutually exclusive per window, so a Windows window using
  the typed `DataTransfer` transport gives up native file drops.

## Related

- [External content in]external-content.md: the other inbound native
  boundary, for text, links and HTML from other apps.
- [Drag and drop]drag-and-drop.md: the in-app context machinery this
  zone deliberately does not use.
- [Styling]styling.md: the full data-attribute contract.