> ## 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.

# Installation

> Install react-mediadrop

export const PromptCard = ({prompt, label = "Use this pre-built prompt to get started faster."}) => {
  const [copied, setCopied] = useState(false);
  const copyPrompt = async () => {
    await navigator.clipboard.writeText(prompt);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };
  const cursorHref = `https://cursor.com/link/prompt?text=${encodeURIComponent(prompt)}`;
  return <div className="mediadrop-prompt-card">
			<style>{`
				.mediadrop-prompt-card {
					--card-bg: #f7f7f5;
					--card-border: #eaeae7;
					--card-text: #1a1a1a;
					--btn-primary-bg: #111111;
					--btn-primary-text: #ffffff;
					--btn-secondary-bg: #ffffff;
					--btn-secondary-border: #1a1a1a;
					--btn-secondary-text: #1a1a1a;
					display: flex;
					flex-direction: column;
					gap: 1rem;
					padding: 1.25rem 1.5rem;
					border-radius: 12px;
					background: var(--card-bg);
					border: 1px solid var(--card-border);
					color: var(--card-text);
					margin: 1.5rem 0;
				}
				.dark .mediadrop-prompt-card {
					--card-bg: #16161a;
					--card-border: #2a2a30;
					--card-text: #e4e4e7;
					--btn-primary-bg: #ffffff;
					--btn-primary-text: #111111;
					--btn-secondary-bg: #16161a;
					--btn-secondary-border: #3a3a42;
					--btn-secondary-text: #e4e4e7;
				}
				.mediadrop-prompt-card__label {
					display: flex;
					align-items: center;
					gap: 0.6rem;
					font-size: 0.95rem;
				}
				.mediadrop-prompt-card__actions {
					display: flex;
					gap: 0.6rem;
					justify-content: flex-end;
				}
				.mediadrop-prompt-card__button {
					display: inline-flex;
					align-items: center;
					gap: 0.4rem;
					padding: 0.5rem 1.1rem;
					border-radius: 999px;
					font-size: 0.85rem;
					font-weight: 500;
					cursor: pointer;
					text-decoration: none;
					white-space: nowrap;
					border: 1px solid transparent;
				}
				.mediadrop-prompt-card__button--primary {
					background: var(--btn-primary-bg);
					color: var(--btn-primary-text);
				}
				.mediadrop-prompt-card__button--secondary {
					background: var(--btn-secondary-bg);
					border-color: var(--btn-secondary-border);
					color: var(--btn-secondary-text);
				}
			`}</style>
			<div className="mediadrop-prompt-card__label">
				<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
					<rect x="7" y="7" width="10" height="10" rx="1" />
					<path d="M9 1v3M15 1v3M9 20v3M15 20v3M1 9h3M1 15h3M20 9h3M20 15h3" />
				</svg>
				<span>{label}</span>
			</div>
			<div className="mediadrop-prompt-card__actions">
				<button type="button" className="mediadrop-prompt-card__button mediadrop-prompt-card__button--primary" onClick={copyPrompt}>
					{copied ? "Copied!" : "Copy prompt"}
				</button>
				<a className="mediadrop-prompt-card__button mediadrop-prompt-card__button--secondary" href={cursorHref}>
					Open in Cursor
				</a>
			</div>
		</div>;
};

export const installPrompt = ['Purpose: integrate react-mediadrop correctly on the first try.', '', '## Setup', '', 'npm install react-mediadrop', '# Requires React 18+.', '# In Next.js App Router (or any RSC setup), add "use client" to the', '# top of the file that calls useMediaDrop — it uses', '# useSyncExternalStore/useEffect and only runs in a Client Component.', '', 'import { useMediaDrop } from "react-mediadrop";', 'import { createXhrUploadTransport } from "react-mediadrop/xhr-upload";', '', '## useMediaDrop(options?) — full option reference', '', '| Option | Type | Default |', '| --- | --- | --- |', '| restrictions | MediaDropRestrictions | — |', '| validator | MediaDropValidator | — |', '| noClick | boolean | false |', '| noKeyboard | boolean | false |', '| noDrag | boolean | false |', '| transport | UploadTransport | — (opts into upload) |', '| concurrency | number | 1 |', '| retries | number | 0 |', '| retryDelays | number[] | — |', '| cancelGraceMs | number | 5000 |', '', 'MediaDropRestrictions: { maxFiles?, minSize?, maxSize?, accept?: string[] | string }', '', 'Return value (always present): files / acceptedFiles / rejectedFiles,', 'isDragActive / isDragAccept / isDragReject, isFocused, isDragGlobal,', 'removeFile(id), clearFiles(), open(), getRootProps(), getInputProps().', 'Return type name: UseMediaDropResult.', '', 'Only present when `transport` is passed: uploadFile(id), uploadAll(),', 'cancelUpload(id), cancelAllUploads(), retryUpload(id). Without', '`transport`, none of these exist on the returned object and', 'TypeScript will not let you call them.', '', '## createXhrUploadTransport(options) — the reference transport', '', '| Option | Type | Default |', '| --- | --- | --- |', '| endpoint | string or (file) => string | required |', '| method | string | "POST" |', '| fieldName | string | "file" |', '| fields | object or (file) => object | — |', '| headers | object or (file) => object | — |', '| withCredentials | boolean | false |', '| formData | boolean | true |', '| isSuccessStatus | (status) => boolean | 200-299 |', '| stallTimeoutMs | number | 0 (disabled) |', '', '## ALWAYS DO', '', '1. ALWAYS add "use client" at the top of any file calling useMediaDrop', '   in a Next.js App Router / RSC project.', '2. ALWAYS import from "react-mediadrop" and "react-mediadrop/xhr-upload"', '   only. Never import from @mediadrop/core or @mediadrop/xhr-upload —', '   those are private, unpublished packages bundled inside', '   react-mediadrop.', '3. ALWAYS pass `transport` before calling uploadFile/uploadAll/', '   cancelUpload/cancelAllUploads/retryUpload.', '4. ALWAYS write a custom transport as exactly one method,', '   upload(file, { onProgress, signal }) => Promise<{ response?: unknown }>.', '5. ALWAYS call the shared withRetry export for any retry logic a', '   custom transport needs of its own.', '', '## NEVER DO', '', '1. NEVER assume pause/resume, S3/tus resumable transports, a', '   remote-provider import, OAuth, or a prebuilt upload widget exist —', '   none are implemented.', '2. NEVER add a retry loop, backoff, or concurrency limit inside a', "   custom transport or inside useMediaDrop's upload methods — the", '   queue and withRetry already own that.', '3. NEVER assume the default shouldRetry retries every failure — it', '   retries only 408, 429, 5xx, and statusless (network) errors; other', '   4xx statuses are not retried.', '4. NEVER treat createFileFingerprint as content-addressed — it hashes', '   name+size+type+lastModified+webkitRelativePath, not file bytes, so', '   two different files with identical metadata can collide.', "5. NEVER name the hook's return type MediaDropResult — the real", '   exported name is UseMediaDropResult.', '', '## Verification checklist', '', 'Before returning any solution, verify:', '- [ ] "use client" is present if this file runs in a Next.js App Router tree.', '- [ ] transport is passed to useMediaDrop before any upload method is called.', '- [ ] restrictions only use maxFiles/minSize/maxSize/accept — no invented options.', '- [ ] No retry/backoff/concurrency logic exists inside a custom transport.', '- [ ] Run `tsc` — it must reject uploadFile/uploadAll/etc. calls when', '      transport was not passed.', '', 'Full reference: https://github.com/autorender/react-mediadrop/tree/main/skills/mediadrop'].join('\n');

react-mediadrop ships as a single npm package — no separate core or
transport package to install.

<PromptCard prompt={installPrompt} />

<Note>
  Requires **React 18+**.
</Note>

<CodeGroup>
  ```sh pnpm theme={"theme":{"light":"github-light","dark":"vesper"}}
  pnpm add react-mediadrop
  ```

  ```sh npm theme={"theme":{"light":"github-light","dark":"vesper"}}
  npm install react-mediadrop
  ```

  ```sh yarn theme={"theme":{"light":"github-light","dark":"vesper"}}
  yarn add react-mediadrop
  ```

  ```sh bun theme={"theme":{"light":"github-light","dark":"vesper"}}
  bun add react-mediadrop
  ```
</CodeGroup>

## Next.js / RSC

<Warning>
  `useMediaDrop` uses `useSyncExternalStore`/`useEffect`, so it only runs
  in a Client Component. Forgetting this is the single most common
  integration mistake — if you hit an error like:

  ```
  Error: You're importing a component that needs `useState`. It only
  works in a Client Component but none of its parents are marked with
  "use client", so they're Server Components by default.
  ```

  add `"use client"` to the top of the file that calls `useMediaDrop`:
</Warning>

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
"use client";

import { useMediaDrop } from "react-mediadrop";
```

<CardGroup cols={2}>
  <Card title="Quickstart" icon="bolt" href="/getting-started/quickstart">
    Wire up a dropzone in a few lines
  </Card>

  <Card title="Agent skill" icon="robot" href="/getting-started/agent-skill">
    Let your AI coding agent integrate this package for you
  </Card>
</CardGroup>
