> ## Documentation Index
> Fetch the complete documentation index at: https://mediadrop.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Core concepts

> The file model, the store, and drag state

`react-mediadrop` tracks every file you add as a `MediaDropFile` object,
backed by a small internal store you never call directly. The engine
that owns this store is bundled inside `react-mediadrop` — it isn't
published or imported separately.

## What does a file look like?

Every file you add becomes a `MediaDropFile`:

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
type MediaDropFile = {
	id: string; // generated, not the browser File's name
	file: File; // the original browser File
	name: string;
	size: number;
	type: string;
	lastModified?: number;
	status: "idle" | "accepted" | "rejected";
	errors: MediaDropError[]; // always an array, even when empty

	// Upload fields, only ever set once an upload is requested for this file:
	uploadStatus?: "queued" | "uploading" | "done" | "error" | "canceled";
	progress?: { loaded: number; total: number | null };
	uploadError?: MediaDropError; // present after a failed/canceled attempt
	uploadResult?: unknown; // whatever the transport resolved with
	uploadAttempts?: number;
};
```

A file's `status` is decided once, when it's added — there's no
re-validation pass later, and **uploading never changes it**. `status`
(validation) and `uploadStatus` (upload lifecycle) are deliberately
separate fields: `getAcceptedFiles()`/`getRejectedFiles()` and `maxFiles`
counting are all based on `status` alone and are completely unaffected by
whether a file has started, finished, or failed uploading. See
[Upload](/guides/upload#status-and-uploadstatus-stay-separate).

## How does the store work?

The engine backing `useMediaDrop` returns an object backed by a small
store:

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
engine.getState(); // { files: MediaDropFile[] }
engine.subscribe(listener); // full-state subscription
engine.subscribe(selector, listener); // fires only when selector's result changes
```

`react-mediadrop` wraps this with `useSyncExternalStore` internally — you
don't call `subscribe` yourself in React, you just read the hook's return
value.

## How do I know when a file is being dragged?

```ts theme={"theme":{"light":"github-light","dark":"vesper"}}
type DragState = {
	isDragActive: boolean; // a drag payload is over this dropzone right now
	isDragAccept: boolean; // best-effort: payload looks acceptable
	isDragReject: boolean; // best-effort: payload looks unacceptable
};
```

`isDragAccept`/`isDragReject` are evaluated from `DataTransferItem.type`
during `dragenter`.

<Info>
  Browsers don't expose the file name while dragging — only after drop. So
  an `accept` restriction like `[".png"]` (an extension) can't be evaluated
  mid-drag, and both flags stay `false` in that case. MIME-based
  restrictions (`"image/png"`, `"image/*"`) work during drag, since the
  MIME type *is* available on `DataTransferItem`. Use MIME types in
  `accept` if you want an accurate drag-time preview.
</Info>

A custom `validator` also participates in this preview, but only as far
as the browser lets it: some browsers hand back a real (if
empty/unreadable) `File` from `DataTransferItem.getAsFile()` mid-drag, and
when that happens the validator runs against it. When the browser doesn't
expose that, the validator is silently skipped for the preview — it still
runs for real at drop time either way.

Don't assume the validator ran during drag; treat it as a bonus, not a
guarantee.

`isDragActive`/`isDragAccept`/`isDragReject` are per-dropzone — there's no
page-wide equivalent by default. `useMediaDrop` additionally returns
`isDragGlobal`: true while a file drag is anywhere on the document,
tracked via its own `dragenter`/`dragleave`/`dragend`/`drop` listeners on
`document` (cleaned up on unmount). Don't assume an equivalent exists
outside of it; wire your own `document` listeners if you need the same
thing somewhere else.

## How does `maxFiles` work across a batch?

Individual restrictions (`accept`, `minSize`, `maxSize`) are evaluated per
file — one bad file never blocks the others in the same batch.

`maxFiles` is different: it's evaluated across the whole file list. When
you call `addFiles` with a batch that would exceed the remaining slots,
files fill the remaining slots **in order** and the overflow is rejected
with `too-many-files`. It does not reject the entire batch.

## Can I use multiple dropzones on one page?

Each `useMediaDrop()` call owns its own drag-enter/leave depth counter and
only reacts to events that bubble to its own root element:

* Multiple independent dropzones on one page do not interfere with each
  other.
* Moving the pointer across child elements inside one dropzone does not
  cause `isDragActive` to flicker (a counter, not a boolean flip, tracks
  enter/leave).
* Overlapping/nested dropzones are **not** specially coordinated today —
  a known limitation, not yet solved.

<CardGroup cols={2}>
  <Card title="Validation" icon="shield-check" href="/guides/validation">
    Restrictions and custom validators
  </Card>

  <Card title="Upload" icon="cloud-arrow-up" href="/guides/upload">
    The queue, concurrency, retry, and cancel
  </Card>
</CardGroup>
