File Dropzone
A drag-and-drop upload area — the magnetic dashed border leans toward the cursor and marches like a marquee, and the file list carries ring progress indicators.
Loading preview…
npx shadcn@latest add https://webberui.com/r/file-dropzone.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
0.12
<FileDropzone />
Installation
npx shadcn@latest add https://webberui.com/r/file-dropzone.jsonOr, once registries are configured in components.json, install it as @webberui/file-dropzone.
Usage
import { FileDropzone } from "@/components/ui/file-dropzone";
<FileDropzone
accept="image/*,.pdf"
maxFiles={4}
maxSize={10 * 1024 * 1024}
onFilesAdded={(files) => console.log(files)}
/>Wiring up a real upload
Without an upload prop, the component shows the animation with simulated progress; to connect a backend, pass an async function and report progress:
<FileDropzone
upload={async (file, onProgress) => {
await uploadToServer(file, {
onUploadProgress: (e) => onProgress(e.loaded / e.total),
});
}}
/>resolve shows the success checkmark; reject shows the failure state.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
onFilesAdded | (files: File[]) => void | — | Fires when files pass validation and are added to the list |
upload | (file: File, onProgress: (p: number) => void) => Promise<void> | — | Custom upload logic; onProgress reports 0–1. When omitted, progress is simulated |
onRemove | (file: File) => void | — | Fires when a file is removed from the list |
onReject | (file: File, reason: "type" | "size" | "count") => void | — | Fires when a file fails validation |
accept | string | — | Accepted file types (same as the native accept); validation applies to drops as well |
multiple | boolean | true | Allow multiple files; when false, a new file replaces the old one |
maxFiles | number | — | Maximum number of files in the list |
maxSize | number | — | Maximum size of a single file (bytes) |
strength | number | 0.12 | Magnetic strength; border offset = the cursor's distance from centre × strength |
label | string | "拖放檔案到這裡,或點擊選取" | Main text of the drop area |
disabled | boolean | false | Disables the whole drop area |
How it works
- Magnetic border: while a drag hovers, the dashed border offsets toward the cursor on a spring (capped at 10px) and scales up slightly, while the dashes march like a marquee
- Dragging in and out of child elements fires
dragenter/dragleaverepeatedly, so a depth counter internally prevents the border state from flickering - The progress ring advances smoothly through SVG
stroke-dashoffsetdriven by a spring, popping a checkmark on completion and showing a warning on failure acceptvalidation supports extensions (.pdf), wildcards (image/*), and full MIME types, and applies to both drops and picks- The input value is cleared after a selection, so the same file can be picked again
Accessibility
- The drop area is
role="button", takes Tab focus, and opens the file picker with Enter / Space - The progress ring carries
role="progressbar"andaria-valuenow, and the success / failure icons have matchingaria-labels - The number of files uploading is announced to screen readers through
aria-live="polite" - When the user has "reduce motion" enabled at the system level, the magnetic offset, marquee, and enter/exit animations are disabled, and the progress ring jumps straight to the current value