Dropzone
A drag-and-drop (or click) upload area wired to useFiles - uploads through your gateway, with composable success, prompt and error states.
A compound dropzone that uploads through a useFiles instance. Drag files in or click to open the picker. With no prefix (and outside folder uploads), the server mints each key and the bytes go straight to storage on a presigned URL, or through your gateway when the adapter can’t sign. With a prefix or a folder path, each file gets an explicit key and streams through the gateway.
"use client";
import { demoFiles } from "@/lib/demo-files";
import {
Dropzone,
DropzoneContent,
DropzoneEmptyState,
DropzoneError,
} from "@/registry/files-sdk/dropzone/dropzone";
const Example = () => {
const files = demoFiles;
return (
<Dropzone accept="image/*" files={files} maxFiles={3} prefix="demo/">
<DropzoneContent />
<DropzoneEmptyState />
<DropzoneError />
</Dropzone>
);
};
export default Example;
Installation
npx shadcn@latest add https://files-sdk.dev/r/dropzone.jsonUsage
import { useFiles } from "files-sdk/react";
import {
Dropzone,
DropzoneContent,
DropzoneEmptyState,
DropzoneError,
} from "@/components/files-sdk/dropzone";
export function Uploader() {
const files = useFiles({ endpoint: "/api/files" });
return (
<Dropzone accept="image/*" files={files} prefix="docs/" maxFiles={5}>
<DropzoneContent />
<DropzoneEmptyState />
<DropzoneError />
</Dropzone>
);
}
DropzoneEmptyState renders the prompt — and keeps rendering it after uploads, so users can keep adding files. DropzoneContent shows a success summary (“3 files uploaded”) once something lands, and DropzoneError surfaces failures (files that don’t match accept, oversize files, files over the maxFiles limit, and failed uploads). Pass your own children to any of them to override the default look (the zone is a <button>, so keep them to phrasing content such as spans, icons and text), or wire onUploaded / onError to render state outside the dropzone entirely.
accept filters the file picker and is enforced on dropped files too, using the same rules: .ext suffixes, type/* wildcards, and exact MIME types.
Folder uploads
Set directory to accept whole folders. The click-to-browse picker becomes a directory picker, dropped folders are traversed recursively, and each file keeps its relative path in the key: dropping my-skill/ with prefix="skills/" uploads skills/my-skill/references/api-guide.md, ready for a tree view in FileBrowser.
"use client";
import { demoFiles } from "@/lib/demo-files";
import {
Dropzone,
DropzoneContent,
DropzoneEmptyState,
DropzoneError,
} from "@/registry/files-sdk/dropzone/dropzone";
const Example = () => {
const files = demoFiles;
return (
<Dropzone directory files={files} prefix="skills/">
<DropzoneContent />
<DropzoneEmptyState />
<DropzoneError />
</Dropzone>
);
};
export default Example;
To let users pick the destination themselves, drive prefix from an input:
import { useState } from "react";
import { useFiles } from "files-sdk/react";
import { Input } from "@/components/ui/input";
import {
Dropzone,
DropzoneContent,
DropzoneEmptyState,
DropzoneError,
} from "@/components/files-sdk/dropzone";
export function FolderUploader() {
const files = useFiles({ endpoint: "/api/files" });
const [destination, setDestination] = useState("references/");
return (
<div className="flex flex-col gap-2">
<Input
aria-label="Destination folder"
onChange={(event) => setDestination(event.target.value)}
value={destination}
/>
<Dropzone directory files={files} prefix={destination}>
<DropzoneContent />
<DropzoneEmptyState />
<DropzoneError />
</Dropzone>
</div>
);
}
With directory set, maxFiles defaults to unlimited; pass a number to cap the total file count per drop.
Props
<Dropzone>
filesUseFilesResult
A `useFiles()` instance — the dropzone uploads through it.
UseFilesResultprefix?string
Key prefix (folder) for explicit keys, e.g. `"docs/"`. Empty = server mints the key.
stringaccept?string
Accepted file types, e.g. `"image/*"` or `".pdf,.docx"` — the file input's `accept` filter, also enforced on dropped files (non-matching files are reported as failed).
stringdirectory?boolean
Accept whole folders: the picker selects a directory and dropped folders are traversed recursively, with relative paths preserved in keys (`prefix + folder/sub/file.ext`).
booleanmaxFiles?number
Max files per drop. Default 1, or unlimited when `directory` is set.
numbermaxSize?number
Max bytes per file; larger files are reported as failed.
numberonUploaded?(entry: UploadedEntry) => void
Called after each successful upload.
(entry: UploadedEntry) => voidonError?(error: Error, file: File) => void
Called for each file that fails to upload or is rejected client-side.
(error: Error, file: File) => voidclassName?string
stringchildren?ReactNode
ReactNode<DropzoneContent>, <DropzoneEmptyState> and <DropzoneError> each accept className and children to override the default look.